use super::commands::WgpuDrawCommand;
use super::raster::{align_to, rasterize_draw_commands_rgba8};
use super::shaders::ShaderModule;
use super::types::Rgba8;
use crate::render::gpu::{GpuCapability, GpuRenderer};
use std::collections::HashMap;
use std::sync::mpsc;
pub struct WgpuRenderer {
device: wgpu::Device,
queue: wgpu::Queue,
clear_pipeline: wgpu::RenderPipeline,
rect_pipeline: wgpu::RenderPipeline,
bind_group_layout: wgpu::BindGroupLayout,
shader_cache: HashMap<String, wgpu::ShaderModule>,
}
impl WgpuRenderer {
pub fn new() -> Result<Self, String> {
pollster::block_on(Self::new_async())
}
async fn new_async() -> Result<Self, String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
display: None,
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.map_err(|_| "wgpu adapter request failed".to_string())?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("rw_wgpu_device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
trace: wgpu::Trace::Off,
})
.await
.map_err(|error| format!("wgpu request_device failed: {error}"))?;
let clear_vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("clear_vs"),
source: wgpu::ShaderSource::Wgsl(super::shaders::FULLSCREEN_QUAD_VS.into()),
});
let clear_fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("clear_fs"),
source: wgpu::ShaderSource::Wgsl(super::shaders::CLEAR_FS.into()),
});
let rect_vs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("rect_vs"),
source: wgpu::ShaderSource::Wgsl(super::shaders::FILL_RECT_VS.into()),
});
let rect_fs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("rect_fs"),
source: wgpu::ShaderSource::Wgsl(super::shaders::FILL_RECT_FS.into()),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("uniform_color_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: Some(wgpu::BufferSize::new(16).unwrap()),
},
count: None,
}],
});
let clear_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("clear_pipeline_layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let clear_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("clear_pipeline"),
layout: Some(&clear_pipeline_layout),
vertex: wgpu::VertexState {
module: &clear_vs_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &clear_fs_module,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("rect_pipeline_layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let rect_vertex_buffer_layout = wgpu::VertexBufferLayout {
array_stride: 8, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
}],
};
let rect_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("rect_pipeline"),
layout: Some(&rect_pipeline_layout),
vertex: wgpu::VertexState {
module: &rect_vs_module,
entry_point: Some("vs_main"),
buffers: &[rect_vertex_buffer_layout],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &rect_fs_module,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Cw,
cull_mode: Some(wgpu::Face::Back),
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let mut shader_cache = HashMap::new();
shader_cache.insert("clear_vs".to_string(), clear_vs_module);
shader_cache.insert("clear_fs".to_string(), clear_fs_module);
shader_cache.insert("rect_vs".to_string(), rect_vs_module);
shader_cache.insert("rect_fs".to_string(), rect_fs_module);
Ok(Self { device, queue, clear_pipeline, rect_pipeline, bind_group_layout, shader_cache })
}
pub fn render_clear_rgba8(
&self,
width: u32,
height: u32,
color: [f64; 4],
) -> Result<Vec<u8>, String> {
self.render_draw_commands_rgba8(
width,
height,
&[WgpuDrawCommand::Clear {
color: Rgba8 {
r: (color[0].clamp(0.0, 1.0) * 255.0) as u8,
g: (color[1].clamp(0.0, 1.0) * 255.0) as u8,
b: (color[2].clamp(0.0, 1.0) * 255.0) as u8,
a: (color[3].clamp(0.0, 1.0) * 255.0) as u8,
},
}],
)
}
pub fn render_clear_gpu(
&self,
width: u32,
height: u32,
color: [f64; 4],
) -> Result<Vec<u8>, String> {
let color_f32: [f32; 4] = [
color[0].clamp(0.0, 1.0) as f32,
color[1].clamp(0.0, 1.0) as f32,
color[2].clamp(0.0, 1.0) as f32,
color[3].clamp(0.0, 1.0) as f32,
];
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("gpu_clear_texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let uniform_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("clear_color_ubo"),
size: 16,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let color_bytes: Vec<u8> = color_f32.iter().flat_map(|f| f.to_le_bytes()).collect();
self.queue.write_buffer(&uniform_buffer, 0, &color_bytes);
let bind_group_layout = self.clear_pipeline.get_bind_group_layout(0);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("clear_bind_group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("gpu_clear_encoder"),
});
{
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("gpu_clear_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
rp.set_pipeline(&self.clear_pipeline);
rp.set_bind_group(0, &bind_group, &[]);
rp.draw(0..3, 0..1); }
let unpadded_bytes_per_row = width * 4;
let padded_bytes_per_row =
super::raster::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("gpu_clear_readback"),
size: padded_bytes_per_row as u64 * height as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
},
},
wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
);
self.queue.submit(Some(encoder.finish()));
let buffer_slice = output_buffer.slice(..);
let (sender, receiver) = mpsc::channel();
buffer_slice.map_async(
wgpu::MapMode::Read,
move |result: Result<(), wgpu::BufferAsyncError>| {
let _ = sender.send(result);
},
);
let _ = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
let map_result =
receiver.recv().map_err(|_| "wgpu map_async callback channel closed".to_string())?;
map_result.map_err(|error| format!("wgpu buffer map failed: {error:?}"))?;
let mapped = buffer_slice.get_mapped_range();
let unpadded = (width * 4) as usize;
let padded = padded_bytes_per_row as usize;
let mut pixels = vec![0u8; unpadded * height as usize];
for row in 0..height as usize {
let src_start = row * padded;
let src_end = src_start + unpadded;
let dst_start = row * unpadded;
let dst_end = dst_start + unpadded;
pixels[dst_start..dst_end].copy_from_slice(&mapped[src_start..src_end]);
}
drop(mapped);
output_buffer.unmap();
Ok(pixels)
}
pub fn render_fill_rect_gpu(
&self,
width: u32,
height: u32,
rects: &[(i32, i32, u32, u32, [f64; 4])],
) -> Result<Vec<u8>, String> {
if rects.is_empty() {
return Ok(vec![0u8; (width * height * 4) as usize]);
}
let w = width as f32;
let h = height as f32;
let mut all_vertices: Vec<f32> = Vec::with_capacity(rects.len() * 12);
let mut bind_groups: Vec<wgpu::BindGroup> = Vec::with_capacity(rects.len());
for &(x, y, rw, rh, color) in rects {
let x0 = (x as f32 / w) * 2.0 - 1.0;
let y0 = 1.0 - (y as f32 / h) * 2.0;
let x1 = ((x.saturating_add(rw as i32)) as f32 / w) * 2.0 - 1.0;
let y1 = 1.0 - ((y.saturating_add(rh as i32)) as f32 / h) * 2.0;
all_vertices.extend_from_slice(&[
x0, y0, x1, y0, x0, y1, x1, y0, x1, y1, x0, y1, ]);
let cf: [f32; 4] = [
color[0].clamp(0.0, 1.0) as f32,
color[1].clamp(0.0, 1.0) as f32,
color[2].clamp(0.0, 1.0) as f32,
color[3].clamp(0.0, 1.0) as f32,
];
let color_bytes: Vec<u8> = cf.iter().flat_map(|f| f.to_le_bytes()).collect();
let uniform_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("rect_color_ubo"),
size: 16,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.queue.write_buffer(&uniform_buffer, 0, &color_bytes);
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("rect_bind_group"),
layout: &self.bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
bind_groups.push(bind_group);
}
let vertex_bytes: Vec<u8> = all_vertices.iter().flat_map(|f| f.to_le_bytes()).collect();
let vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("rect_vertex_buffer"),
size: vertex_bytes.len() as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.queue.write_buffer(&vertex_buffer, 0, &vertex_bytes);
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("gpu_rect_texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("gpu_rect_encoder"),
});
{
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("gpu_rect_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
rp.set_pipeline(&self.rect_pipeline);
rp.set_vertex_buffer(0, vertex_buffer.slice(..));
for (i, bg) in bind_groups.iter().enumerate() {
rp.set_bind_group(0, bg, &[]);
let start = (i * 6) as u32;
let end = ((i + 1) * 6) as u32;
rp.draw(start..end, 0..1);
}
}
let unpadded_bytes_per_row = width * 4;
let padded_bytes_per_row =
super::raster::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("gpu_rect_readback"),
size: padded_bytes_per_row as u64 * height as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
},
},
wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
);
self.queue.submit(Some(encoder.finish()));
let buffer_slice = output_buffer.slice(..);
let (sender, receiver) = std::sync::mpsc::channel();
buffer_slice.map_async(
wgpu::MapMode::Read,
move |result: Result<(), wgpu::BufferAsyncError>| {
let _ = sender.send(result);
},
);
let _ = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
let map_result =
receiver.recv().map_err(|_| "wgpu map_async callback channel closed".to_string())?;
map_result.map_err(|error| format!("wgpu buffer map failed: {error:?}"))?;
let mapped = buffer_slice.get_mapped_range();
let unpadded = (width * 4) as usize;
let padded = padded_bytes_per_row as usize;
let mut pixels = vec![0u8; unpadded * height as usize];
for row in 0..height as usize {
let src_start = row * padded;
let src_end = src_start + unpadded;
let dst_start = row * unpadded;
let dst_end = dst_start + unpadded;
pixels[dst_start..dst_end].copy_from_slice(&mapped[src_start..src_end]);
}
drop(mapped);
output_buffer.unmap();
Ok(pixels)
}
#[allow(clippy::type_complexity)]
pub fn render_stroke_rect_gpu(
&self,
width: u32,
height: u32,
rects: &[((i32, i32, u32, u32, u32), [f64; 4])],
) -> Result<Vec<u8>, String> {
if rects.is_empty() {
return Ok(vec![0u8; (width * height * 4) as usize]);
}
let mut fill_rects: Vec<((i32, i32, u32, u32), [f64; 4])> =
Vec::with_capacity(rects.len() * 4);
for &((x, y, rw, rh, sw), color) in rects {
let sw_i = sw as i32;
fill_rects.push(((x, y, rw, sw), color));
fill_rects.push(((x, y + rh as i32 - sw_i, rw, sw), color));
let inner_h = rh.saturating_sub(sw * 2);
if inner_h > 0 {
fill_rects.push(((x, y + sw_i, sw, inner_h), color));
}
if inner_h > 0 {
fill_rects.push(((x + rw as i32 - sw_i, y + sw_i, sw, inner_h), color));
}
}
let rect_input: Vec<(i32, i32, u32, u32, [f64; 4])> =
fill_rects.into_iter().map(|((x, y, w, h), color)| (x, y, w, h, color)).collect();
self.render_fill_rect_gpu(width, height, &rect_input)
}
#[allow(clippy::type_complexity)]
pub fn render_draw_line_gpu(
&self,
width: u32,
height: u32,
lines: &[((i32, i32, i32, i32, u32), [f64; 4])],
) -> Result<Vec<u8>, String> {
if lines.is_empty() {
return Ok(vec![0u8; (width * height * 4) as usize]);
}
let mut fill_rects: Vec<(i32, i32, u32, u32, [f64; 4])> = Vec::with_capacity(lines.len());
for &((x1, y1, x2, y2, lw), color) in lines {
let lw_i = lw as i32;
let dx = (x2 - x1).abs();
let dy = (y2 - y1).abs();
if dx >= dy {
let min_x = x1.min(x2);
let line_len = dx.max(1) as u32;
fill_rects.push((min_x, y1 - lw_i / 2, line_len, lw, color));
} else {
let min_y = y1.min(y2);
let line_len = dy.max(1) as u32;
fill_rects.push((x1 - lw_i / 2, min_y, lw, line_len, color));
}
}
self.render_fill_rect_gpu(width, height, &fill_rects)
}
#[allow(clippy::type_complexity)]
pub fn render_fill_circle_gpu(
&self,
width: u32,
height: u32,
circles: &[((i32, i32, u32), [f64; 4])], ) -> Result<Vec<u8>, String> {
if circles.is_empty() {
return Ok(vec![0u8; (width * height * 4) as usize]);
}
let mut fill_rects: Vec<(i32, i32, u32, u32, [f64; 4])> = Vec::new();
for &((cx, cy, radius), color) in circles {
let r = radius as i32;
for dx in -r..=r {
let half_height = ((r * r - dx * dx) as f64).sqrt() as i32;
let x = cx + dx;
let y1 = cy - half_height;
let strip_height = (half_height * 2) as u32;
if strip_height > 0 {
fill_rects.push((x, y1, 1, strip_height, color));
}
}
}
let rects: Vec<(i32, i32, u32, u32, [f64; 4])> = fill_rects;
self.render_fill_rect_gpu(width, height, &rects)
}
pub fn render_draw_commands_rgba8(
&self,
width: u32,
height: u32,
commands: &[WgpuDrawCommand],
) -> Result<Vec<u8>, String> {
let pixels = rasterize_draw_commands_rgba8(width, height, commands)?;
self.upload_and_readback_rgba8(width, height, &pixels)
}
pub fn upload_rgba8_and_readback(
&self,
width: u32,
height: u32,
rgba8: &[u8],
) -> Result<Vec<u8>, String> {
if rgba8.len() != (width * height * 4) as usize {
return Err("rgba8 input length does not match width*height*4".to_string());
}
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("rw_wgpu_offscreen_texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let bytes_per_pixel = 4u32;
let unpadded_bytes_per_row = width * bytes_per_pixel;
let padded_bytes_per_row =
align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
let output_buffer_size = padded_bytes_per_row as u64 * height as u64;
self.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba8,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(unpadded_bytes_per_row),
rows_per_image: Some(height),
},
wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
);
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("rw_wgpu_readback_buffer"),
size: output_buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("rw_wgpu_encoder"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
},
},
wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
);
self.queue.submit(Some(encoder.finish()));
let buffer_slice = output_buffer.slice(..);
let (sender, receiver) = mpsc::channel();
buffer_slice.map_async(
wgpu::MapMode::Read,
move |result: Result<(), wgpu::BufferAsyncError>| {
let _ = sender.send(result);
},
);
let _ = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
let map_result =
receiver.recv().map_err(|_| "wgpu map_async callback channel closed".to_string())?;
map_result.map_err(|error| format!("wgpu buffer map failed: {error:?}"))?;
let mapped = buffer_slice.get_mapped_range();
let mut pixels = vec![0u8; (width * height * bytes_per_pixel) as usize];
for row in 0..height as usize {
let src_start = row * padded_bytes_per_row as usize;
let src_end = src_start + unpadded_bytes_per_row as usize;
let dst_start = row * unpadded_bytes_per_row as usize;
let dst_end = dst_start + unpadded_bytes_per_row as usize;
pixels[dst_start..dst_end].copy_from_slice(&mapped[src_start..src_end]);
}
drop(mapped);
output_buffer.unmap();
Ok(pixels)
}
fn upload_and_readback_rgba8(
&self,
width: u32,
height: u32,
rgba8: &[u8],
) -> Result<Vec<u8>, String> {
self.upload_rgba8_and_readback(width, height, rgba8)
}
pub fn shader_modules(&self) -> Vec<ShaderModule> {
vec![
ShaderModule::FillRect,
ShaderModule::DrawImage,
ShaderModule::DrawText,
ShaderModule::FillRoundedRect,
ShaderModule::FillCircle,
]
}
pub fn get_shader_module(&self, name: &str) -> Option<&wgpu::ShaderModule> {
self.shader_cache.get(name)
}
}
impl GpuRenderer for WgpuRenderer {
fn initialize(&mut self) -> Result<(), String> {
Ok(())
}
fn capabilities(&self) -> &[GpuCapability] {
&[GpuCapability::Basic2D, GpuCapability::Texture]
}
fn has_capability(&self, capability: GpuCapability) -> bool {
matches!(capability, GpuCapability::Basic2D | GpuCapability::Texture)
}
fn render_frame(&mut self) -> Result<(), String> {
Ok(())
}
fn memory_usage(&self) -> u64 {
0
}
fn vendor_info(&self) -> &str {
"wgpu (WGPU)"
}
}