use conrod_core::{
image,
mesh::{self, Mesh},
render,
text::rt,
Rect, Scalar,
};
use std::collections::{HashMap, HashSet};
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,
glyph_cache_tex: wgpu::Texture,
_default_image_tex: wgpu::Texture,
default_bind_group: wgpu::BindGroup,
sampler: wgpu::Sampler,
mesh: Mesh,
dst_format: wgpu::TextureFormat,
dst_sample_count: u32,
bind_groups: HashMap<image::Id, wgpu::BindGroup>,
render_pipelines: HashMap<wgpu::TextureComponentType, Pipeline>,
}
struct Pipeline {
bind_group_layout: wgpu::BindGroupLayout,
render_pipeline: wgpu::RenderPipeline,
}
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 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 },
SetPipeline { pipeline: &'a wgpu::RenderPipeline },
}
const GLYPH_TEX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
const GLYPH_TEX_COMPONENT_TY: wgpu::TextureComponentType = wgpu::TextureComponentType::Uint;
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 Image {
pub fn texture_component_type(&self) -> wgpu::TextureComponentType {
texture_format_to_component_type(self.texture_format)
}
}
impl Renderer {
pub fn new(
device: &wgpu::Device,
dst_sample_count: u32,
dst_format: wgpu::TextureFormat,
) -> Self {
let glyph_cache_dims = mesh::DEFAULT_GLYPH_CACHE_DIMS;
Self::with_glyph_cache_dimensions(device, dst_sample_count, dst_format, glyph_cache_dims)
}
pub fn with_glyph_cache_dimensions(
device: &wgpu::Device,
dst_sample_count: 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 default_image_tex_desc = default_image_tex_desc();
let default_image_tex = device.create_texture(&default_image_tex_desc);
let sampler_desc = sampler_desc();
let sampler = device.create_sampler(&sampler_desc);
let mut render_pipelines = HashMap::new();
let default_tex_component_ty = texture_format_to_component_type(DEFAULT_IMAGE_TEX_FORMAT);
let bind_group_layout = bind_group_layout(device, default_tex_component_ty);
let pipeline_layout = pipeline_layout(device, &bind_group_layout);
let render_pipeline = render_pipeline(
device,
&pipeline_layout,
&vs_mod,
&fs_mod,
dst_format,
dst_sample_count,
);
let default_bind_group = bind_group(
device,
&bind_group_layout,
&glyph_cache_tex,
&sampler,
&default_image_tex,
);
let default_pipeline = Pipeline {
bind_group_layout,
render_pipeline,
};
render_pipelines.insert(default_tex_component_ty, default_pipeline);
let bind_groups = Default::default();
Self {
vs_mod,
fs_mod,
glyph_cache_tex,
_default_image_tex: default_image_tex,
default_bind_group,
sampler,
dst_format,
dst_sample_count,
bind_groups,
render_pipelines,
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 Renderer {
ref mut bind_groups,
ref mut render_pipelines,
ref mut mesh,
ref vs_mod,
ref fs_mod,
ref default_bind_group,
ref glyph_cache_tex,
ref sampler,
dst_format,
dst_sample_count,
..
} = *self;
let mut commands = vec![];
let default_tct = texture_format_to_component_type(DEFAULT_IMAGE_TEX_FORMAT);
let unique_tex_component_types: HashSet<_> = image_map
.values()
.map(|img| img.texture_component_type())
.chain(Some(default_tct))
.collect();
bind_groups.retain(|k, _| image_map.contains_key(k));
render_pipelines.retain(|tct, _| unique_tex_component_types.contains(tct));
for (id, img) in image_map.iter() {
if bind_groups.contains_key(id) {
continue;
}
let tct = img.texture_component_type();
let pipeline = render_pipelines.entry(tct).or_insert_with(|| {
let bind_group_layout = bind_group_layout(device, tct);
let pipeline_layout = pipeline_layout(device, &bind_group_layout);
let render_pipeline = render_pipeline(
device,
&pipeline_layout,
vs_mod,
fs_mod,
dst_format,
dst_sample_count,
);
Pipeline {
bind_group_layout,
render_pipeline,
}
});
let bind_group = bind_group(
device,
&pipeline.bind_group_layout,
&glyph_cache_tex,
sampler,
&img.texture,
);
bind_groups.insert(*id, bind_group);
}
let vertices = mesh.vertices();
let vertices_bytes = vertices_as_bytes(vertices);
let usage = wgpu::BufferUsage::VERTEX;
let vertex_buffer = device.create_buffer_with_data(vertices_bytes, usage);
#[derive(PartialEq)]
enum BindGroup {
Default,
Image(image::Id),
}
let mut bind_group = None;
for command in 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 pipeline = &render_pipelines[&default_tct].render_pipeline;
let cmd = RenderPassCommand::SetPipeline { pipeline };
commands.push(cmd);
let cmd = RenderPassCommand::SetBindGroup {
bind_group: 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 {
let expected_tct = image_map[&image_id].texture_component_type();
let current_tct = bind_group.as_ref().map(|bg| match *bg {
BindGroup::Default => default_tct,
BindGroup::Image(id) => image_map[&id].texture_component_type(),
});
if current_tct != Some(expected_tct) {
let pipeline = &render_pipelines[&expected_tct].render_pipeline;
let cmd = RenderPassCommand::SetPipeline { pipeline };
commands.push(cmd);
}
bind_group = expected_bind_group;
let cmd = RenderPassCommand::SetBindGroup {
bind_group: &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 {
vertex_buffer,
commands,
}
}
}
impl<'a> GlyphCacheCommand<'a> {
pub fn create_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer {
device.create_buffer_with_data(&self.glyph_cache_pixel_buffer, wgpu::BufferUsage::COPY_SRC)
}
pub fn buffer_copy_view<'b>(&self, buffer: &'b wgpu::Buffer) -> wgpu::BufferCopyView<'b> {
wgpu::BufferCopyView {
buffer,
offset: 0,
bytes_per_row: self.width,
rows_per_image: 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<'static> {
let depth = 1;
let texture_extent = wgpu::Extent3d {
width,
height,
depth,
};
wgpu::TextureDescriptor {
label: Some("conrod_wgpu_glyph_cache_texture"),
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<'static> {
let width = 64;
let height = 64;
let depth = 1;
let texture_extent = wgpu::Extent3d {
width,
height,
depth,
};
wgpu::TextureDescriptor {
label: Some("conrod_wgpu_image_texture"),
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: wgpu::CompareFunction::Always,
}
}
fn bind_group_layout(
device: &wgpu::Device,
img_tex_component_ty: wgpu::TextureComponentType,
) -> wgpu::BindGroupLayout {
let glyph_cache_texture_binding = wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: false,
component_type: GLYPH_TEX_COMPONENT_TY,
dimension: wgpu::TextureViewDimension::D2,
},
};
let sampler_binding = wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler { comparison: false },
};
let image_texture_binding = wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: false,
component_type: img_tex_component_ty,
dimension: wgpu::TextureViewDimension::D2,
},
};
let bindings = &[
glyph_cache_texture_binding,
sampler_binding,
image_texture_binding,
];
let desc = wgpu::BindGroupLayoutDescriptor {
label: Some("conrod_bind_group_layout"),
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 label = Some("conrod_bind_group");
let desc = wgpu::BindGroupDescriptor {
label,
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,
dst_sample_count: 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 vertex_state_desc = wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[vertex_buffer_desc],
};
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,
vertex_state: vertex_state_desc,
sample_count: dst_sample_count,
sample_mask: !0,
alpha_to_coverage_enabled: false,
};
device.create_render_pipeline(&desc)
}
fn vertices_as_bytes(s: &[mesh::Vertex]) -> &[u8] {
let len = s.len() * std::mem::size_of::<mesh::Vertex>();
let ptr = s.as_ptr() as *const u8;
unsafe { std::slice::from_raw_parts(ptr, len) }
}
fn texture_format_to_component_type(format: wgpu::TextureFormat) -> wgpu::TextureComponentType {
match format {
wgpu::TextureFormat::R8Uint
| wgpu::TextureFormat::R16Uint
| wgpu::TextureFormat::Rg8Uint
| wgpu::TextureFormat::R32Uint
| wgpu::TextureFormat::Rg16Uint
| wgpu::TextureFormat::Rgba8Uint
| wgpu::TextureFormat::Rg32Uint
| wgpu::TextureFormat::Rgba16Uint
| wgpu::TextureFormat::Rgba32Uint => wgpu::TextureComponentType::Uint,
wgpu::TextureFormat::R8Sint
| wgpu::TextureFormat::R16Sint
| wgpu::TextureFormat::Rg8Sint
| wgpu::TextureFormat::R32Sint
| wgpu::TextureFormat::Rg16Sint
| wgpu::TextureFormat::Rgba8Sint
| wgpu::TextureFormat::Rg32Sint
| wgpu::TextureFormat::Rgba16Sint
| wgpu::TextureFormat::Rgba32Sint => wgpu::TextureComponentType::Sint,
wgpu::TextureFormat::R8Unorm
| wgpu::TextureFormat::R8Snorm
| wgpu::TextureFormat::R16Float
| wgpu::TextureFormat::R32Float
| wgpu::TextureFormat::Rg8Unorm
| wgpu::TextureFormat::Rg8Snorm
| wgpu::TextureFormat::Rg16Float
| wgpu::TextureFormat::Rg11b10Float
| wgpu::TextureFormat::Rg32Float
| wgpu::TextureFormat::Rgba8Snorm
| wgpu::TextureFormat::Rgba16Float
| wgpu::TextureFormat::Rgba32Float
| wgpu::TextureFormat::Rgba8Unorm
| wgpu::TextureFormat::Rgba8UnormSrgb
| wgpu::TextureFormat::Bgra8Unorm
| wgpu::TextureFormat::Bgra8UnormSrgb
| wgpu::TextureFormat::Rgb10a2Unorm
| wgpu::TextureFormat::Depth32Float
| wgpu::TextureFormat::Depth24Plus
| wgpu::TextureFormat::Depth24PlusStencil8 => wgpu::TextureComponentType::Float,
}
}