use conrod_core::{
image,
mesh::{self, Mesh},
render,
text::rt,
Rect, Scalar,
};
use std::collections::HashMap;
pub struct Image {
pub texture: wgpu::Texture,
pub texture_format: wgpu::TextureFormat,
pub width: u32,
pub height: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Vertex {
pub position: [f32; 2],
pub tex_coords: [f32; 2],
pub rgba: [f32; 4],
pub mode: u32,
}
pub struct Renderer {
_vs_mod: wgpu::ShaderModule,
_fs_mod: wgpu::ShaderModule,
bind_group_layout: wgpu::BindGroupLayout,
render_pipeline: wgpu::RenderPipeline,
glyph_cache_tex: wgpu::Texture,
_default_image_tex: wgpu::Texture,
default_bind_group: wgpu::BindGroup,
sampler: wgpu::Sampler,
mesh: Mesh,
bind_groups: HashMap<image::Id, wgpu::BindGroup>,
}
pub struct GlyphCacheCommand<'a> {
pub glyph_cache_pixel_buffer: &'a [u8],
pub glyph_cache_texture: &'a wgpu::Texture,
pub width: u32,
pub height: u32,
}
pub struct Render<'a> {
pub pipeline: &'a wgpu::RenderPipeline,
pub vertex_buffer: wgpu::Buffer,
pub commands: Vec<RenderPassCommand<'a>>,
}
pub enum RenderPassCommand<'a> {
SetScissor {
top_left: [u32; 2],
dimensions: [u32; 2],
},
Draw { vertex_range: std::ops::Range<u32> },
SetBindGroup { bind_group: &'a wgpu::BindGroup },
}
const GLYPH_TEX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
const DEFAULT_IMAGE_TEX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
impl mesh::ImageDimensions for Image {
fn dimensions(&self) -> [u32; 2] {
[self.width, self.height]
}
}
impl Renderer {
pub fn new(
device: &wgpu::Device,
msaa_samples: u32,
dst_format: wgpu::TextureFormat,
) -> Self {
let glyph_cache_dims = mesh::DEFAULT_GLYPH_CACHE_DIMS;
Self::with_glyph_cache_dimensions(device, msaa_samples, dst_format, glyph_cache_dims)
}
pub fn with_glyph_cache_dimensions(
device: &wgpu::Device,
msaa_samples: u32,
dst_format: wgpu::TextureFormat,
glyph_cache_dims: [u32; 2],
) -> Self {
assert_eq!(
glyph_cache_dims[0] % 256,
0,
"wgpu glyph cache width must be multiple of 256"
);
let mesh = Mesh::with_glyph_cache_dimensions(glyph_cache_dims);
let vs = include_bytes!("shaders/vert.spv");
let vs_spirv = wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
.expect("failed to read hard-coded SPIRV");
let vs_mod = device.create_shader_module(&vs_spirv);
let fs = include_bytes!("shaders/frag.spv");
let fs_spirv = wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
.expect("failed to read hard-coded SPIRV");
let fs_mod = device.create_shader_module(&fs_spirv);
let glyph_cache_tex_desc = glyph_cache_tex_desc(glyph_cache_dims);
let glyph_cache_tex = device.create_texture(&glyph_cache_tex_desc);
let sampler_desc = sampler_desc();
let sampler = device.create_sampler(&sampler_desc);
let bind_group_layout = bind_group_layout(device);
let pipeline_layout = pipeline_layout(device, &bind_group_layout);
let render_pipeline = render_pipeline(
device,
&pipeline_layout,
&vs_mod,
&fs_mod,
dst_format,
msaa_samples,
);
let default_image_tex_desc = default_image_tex_desc();
let default_image_tex = device.create_texture(&default_image_tex_desc);
let default_bind_group = bind_group(
device,
&bind_group_layout,
&glyph_cache_tex,
&sampler,
&default_image_tex,
);
let bind_groups = Default::default();
Self {
_vs_mod: vs_mod,
_fs_mod: fs_mod,
glyph_cache_tex,
_default_image_tex: default_image_tex,
default_bind_group,
sampler,
bind_group_layout,
bind_groups,
render_pipeline,
mesh,
}
}
pub fn commands(&self) -> mesh::Commands {
self.mesh.commands()
}
pub fn fill<'a, P>(
&'a mut self,
image_map: &image::Map<Image>,
viewport: [f32; 4],
scale_factor: f64,
primitives: P,
) -> Result<Option<GlyphCacheCommand<'a>>, rt::gpu_cache::CacheWriteErr>
where
P: render::PrimitiveWalker,
{
let [vp_l, vp_t, vp_r, vp_b] = viewport;
let lt = [vp_l as Scalar, vp_t as Scalar];
let rb = [vp_r as Scalar, vp_b as Scalar];
let viewport = Rect::from_corners(lt, rb);
let fill = self
.mesh
.fill(viewport, scale_factor, image_map, primitives)?;
let glyph_cache_cmd = match fill.glyph_cache_requires_upload {
false => None,
true => {
let (width, height) = self.mesh.glyph_cache().dimensions();
Some(GlyphCacheCommand {
glyph_cache_pixel_buffer: self.mesh.glyph_cache_pixel_buffer(),
glyph_cache_texture: &self.glyph_cache_tex,
width,
height,
})
}
};
Ok(glyph_cache_cmd)
}
pub fn render(&mut self, device: &wgpu::Device, image_map: &image::Map<Image>) -> Render {
let mut commands = vec![];
self.bind_groups.retain(|k, _| image_map.contains_key(k));
for (id, img) in image_map.iter() {
if self.bind_groups.contains_key(id) {
continue;
}
let bind_group = bind_group(
device,
&self.bind_group_layout,
&self.glyph_cache_tex,
&self.sampler,
&img.texture,
);
self.bind_groups.insert(*id, bind_group);
}
let vertices = self.mesh.vertices();
let vertices = conv_vertex_buffer(vertices);
let vertex_buffer = device
.create_buffer_mapped(vertices.len(), wgpu::BufferUsage::VERTEX)
.fill_from_slice(vertices);
#[derive(PartialEq)]
enum BindGroup {
Default,
Image(image::Id),
}
let mut bind_group = None;
for command in self.mesh.commands() {
match command {
mesh::Command::Scizzor(s) => {
let top_left = [s.top_left[0] as u32, s.top_left[1] as u32];
let dimensions = s.dimensions;
let cmd = RenderPassCommand::SetScissor {
top_left,
dimensions,
};
commands.push(cmd);
}
mesh::Command::Draw(draw) => match draw {
mesh::Draw::Plain(vertex_range) => {
let vertex_count = vertex_range.len();
if vertex_count <= 0 {
continue;
}
if bind_group.is_none() {
bind_group = Some(BindGroup::Default);
let cmd = RenderPassCommand::SetBindGroup {
bind_group: &self.default_bind_group,
};
commands.push(cmd);
}
let cmd = RenderPassCommand::Draw {
vertex_range: vertex_range.start as u32..vertex_range.end as u32,
};
commands.push(cmd);
}
mesh::Draw::Image(image_id, vertex_range) => {
let vertex_count = vertex_range.len();
if vertex_count == 0 {
continue;
}
let expected_bind_group = Some(BindGroup::Image(image_id));
if bind_group != expected_bind_group {
bind_group = expected_bind_group;
let cmd = RenderPassCommand::SetBindGroup {
bind_group: &self.bind_groups[&image_id],
};
commands.push(cmd);
}
let cmd = RenderPassCommand::Draw {
vertex_range: vertex_range.start as u32..vertex_range.end as u32,
};
commands.push(cmd);
}
},
}
}
Render {
pipeline: &self.render_pipeline,
vertex_buffer,
commands,
}
}
}
impl<'a> GlyphCacheCommand<'a> {
pub fn create_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer {
let len = self.glyph_cache_pixel_buffer.len();
device
.create_buffer_mapped(len, wgpu::BufferUsage::COPY_SRC)
.fill_from_slice(&self.glyph_cache_pixel_buffer)
}
pub fn buffer_copy_view<'b>(&self, buffer: &'b wgpu::Buffer) -> wgpu::BufferCopyView<'b> {
wgpu::BufferCopyView {
buffer,
offset: 0,
row_pitch: self.width,
image_height: self.height,
}
}
pub fn texture_copy_view(&self) -> wgpu::TextureCopyView {
wgpu::TextureCopyView {
texture: &self.glyph_cache_texture,
mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d::ZERO,
}
}
pub fn encode(&self, buffer: &wgpu::Buffer, encoder: &mut wgpu::CommandEncoder) {
let buffer_copy_view = self.buffer_copy_view(&buffer);
let texture_copy_view = self.texture_copy_view();
let extent = self.extent();
encoder.copy_buffer_to_texture(buffer_copy_view, texture_copy_view, extent);
}
pub fn extent(&self) -> wgpu::Extent3d {
wgpu::Extent3d {
width: self.width,
height: self.height,
depth: 1,
}
}
pub fn load_buffer_and_encode(&self, device: &wgpu::Device, e: &mut wgpu::CommandEncoder) {
let buffer = self.create_buffer(&device);
self.encode(&buffer, e);
}
}
fn glyph_cache_tex_desc([width, height]: [u32; 2]) -> wgpu::TextureDescriptor {
let depth = 1;
let texture_extent = wgpu::Extent3d {
width,
height,
depth,
};
wgpu::TextureDescriptor {
size: texture_extent,
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: GLYPH_TEX_FORMAT,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
}
}
fn default_image_tex_desc() -> wgpu::TextureDescriptor {
let width = 64;
let height = 64;
let depth = 1;
let texture_extent = wgpu::Extent3d {
width,
height,
depth,
};
wgpu::TextureDescriptor {
size: texture_extent,
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: DEFAULT_IMAGE_TEX_FORMAT,
usage: wgpu::TextureUsage::SAMPLED,
}
}
fn sampler_desc() -> wgpu::SamplerDescriptor {
wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
lod_min_clamp: -100.0,
lod_max_clamp: 100.0,
compare_function: wgpu::CompareFunction::Always,
}
}
fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
let glyph_cache_texture_binding = wgpu::BindGroupLayoutBinding {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: false,
dimension: wgpu::TextureViewDimension::D2,
},
};
let sampler_binding = wgpu::BindGroupLayoutBinding {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler,
};
let image_texture_binding = wgpu::BindGroupLayoutBinding {
binding: 2,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: false,
dimension: wgpu::TextureViewDimension::D2,
},
};
let bindings = &[
glyph_cache_texture_binding,
sampler_binding,
image_texture_binding,
];
let desc = wgpu::BindGroupLayoutDescriptor { bindings };
device.create_bind_group_layout(&desc)
}
fn bind_group(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
glyph_cache_tex: &wgpu::Texture,
sampler: &wgpu::Sampler,
image: &wgpu::Texture,
) -> wgpu::BindGroup {
let glyph_cache_tex_view = glyph_cache_tex.create_default_view();
let glyph_cache_tex_binding = wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(&glyph_cache_tex_view),
};
let sampler_binding = wgpu::Binding {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
};
let image_tex_view = image.create_default_view();
let image_tex_binding = wgpu::Binding {
binding: 2,
resource: wgpu::BindingResource::TextureView(&image_tex_view),
};
let bindings = &[glyph_cache_tex_binding, sampler_binding, image_tex_binding];
let desc = wgpu::BindGroupDescriptor { layout, bindings };
device.create_bind_group(&desc)
}
fn pipeline_layout(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::PipelineLayout {
let desc = wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&bind_group_layout],
};
device.create_pipeline_layout(&desc)
}
fn vertex_attrs() -> [wgpu::VertexAttributeDescriptor; 4] {
let position_offset = 0;
let position_size = std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress;
let tex_coords_offset = position_offset + position_size;
let tex_coords_size = position_size;
let rgba_offset = tex_coords_offset + tex_coords_size;
let rgba_size = std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress;
let mode_offset = rgba_offset + rgba_size;
[
wgpu::VertexAttributeDescriptor {
format: wgpu::VertexFormat::Float2,
offset: position_offset,
shader_location: 0,
},
wgpu::VertexAttributeDescriptor {
format: wgpu::VertexFormat::Float2,
offset: tex_coords_offset,
shader_location: 1,
},
wgpu::VertexAttributeDescriptor {
format: wgpu::VertexFormat::Float4,
offset: rgba_offset,
shader_location: 2,
},
wgpu::VertexAttributeDescriptor {
format: wgpu::VertexFormat::Uint,
offset: mode_offset,
shader_location: 3,
},
]
}
fn render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
vs_mod: &wgpu::ShaderModule,
fs_mod: &wgpu::ShaderModule,
dst_format: wgpu::TextureFormat,
msaa_samples: u32,
) -> wgpu::RenderPipeline {
let vs_desc = wgpu::ProgrammableStageDescriptor {
module: &vs_mod,
entry_point: "main",
};
let fs_desc = wgpu::ProgrammableStageDescriptor {
module: &fs_mod,
entry_point: "main",
};
let raster_desc = wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None,
depth_bias: 0,
depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0,
};
let color_state_desc = wgpu::ColorStateDescriptor {
format: dst_format,
color_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha_blend: wgpu::BlendDescriptor {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
write_mask: wgpu::ColorWrite::ALL,
};
let vertex_attrs = vertex_attrs();
let vertex_buffer_desc = wgpu::VertexBufferDescriptor {
stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &vertex_attrs[..],
};
let desc = wgpu::RenderPipelineDescriptor {
layout,
vertex_stage: vs_desc,
fragment_stage: Some(fs_desc),
rasterization_state: Some(raster_desc),
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[color_state_desc],
depth_stencil_state: None,
index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[vertex_buffer_desc],
sample_count: msaa_samples,
sample_mask: !0,
alpha_to_coverage_enabled: false,
};
device.create_render_pipeline(&desc)
}
fn conv_vertex_buffer(buffer: &[mesh::Vertex]) -> &[Vertex] {
unsafe { std::mem::transmute(buffer) }
}