use glyph_brush::ab_glyph::{point, Rect};
use super::text_cache::TextCache;
#[derive(Debug)]
pub struct TextPipeline {
pub inner: wgpu::RenderPipeline,
pub sampler: wgpu::Sampler,
pub bind_group_layout: wgpu::BindGroupLayout,
}
impl TextPipeline {
pub(crate) fn new(device: &wgpu::Device, multisample: wgpu::MultisampleState) -> TextPipeline {
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("wgpu-text Matrix, Texture and Sampler Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let shader =
device.create_shader_module(wgpu::include_wgsl!("../../../res/shader/text.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("wgpu-text Render Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let inner = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("wgpu-text Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[TextVertex::buffer_layout()],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: Some(wgpu::IndexFormat::Uint16),
..Default::default()
},
depth_stencil: None,
multisample,
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8UnormSrgb,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("wgpu-text Cache Texture Sampler"),
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,
..Default::default()
});
Self {
inner,
sampler,
bind_group_layout,
}
}
pub(crate) fn draw<'pass>(
&'pass self,
cache: &'pass TextCache,
rpass: &mut wgpu::RenderPass<'pass>,
) {
let vertices = cache.vertices();
if vertices != 0 {
rpass.set_pipeline(&self.inner);
rpass.set_vertex_buffer(0, cache.vertex_buffer.slice(..));
rpass.set_bind_group(0, &cache.bind_group, &[]);
rpass.draw(0..4, 0..vertices);
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct TextVertex {
top_left: [f32; 3],
bottom_right: [f32; 2],
tex_top_left: [f32; 2],
tex_bottom_right: [f32; 2],
color: [f32; 4],
}
impl TextVertex {
pub fn to_vertex(
glyph_brush::GlyphVertex {
mut tex_coords,
pixel_coords,
bounds,
extra,
}: glyph_brush::GlyphVertex,
) -> TextVertex {
let bounds = bounds;
let mut rect = Rect {
min: point(pixel_coords.min.x, pixel_coords.min.y),
max: point(pixel_coords.max.x, pixel_coords.max.y),
};
if rect.max.x > bounds.max.x {
let old_width = rect.width();
rect.max.x = bounds.max.x;
tex_coords.max.x = tex_coords.min.x + tex_coords.width() * rect.width() / old_width;
}
if rect.min.x < bounds.min.x {
let old_width = rect.width();
rect.min.x = bounds.min.x;
tex_coords.min.x = tex_coords.max.x - tex_coords.width() * rect.width() / old_width;
}
if rect.max.y > bounds.max.y {
let old_height = rect.height();
rect.max.y = bounds.max.y;
tex_coords.max.y = tex_coords.min.y + tex_coords.height() * rect.height() / old_height;
}
if rect.min.y < bounds.min.y {
let old_height = rect.height();
rect.min.y = bounds.min.y;
tex_coords.min.y = tex_coords.max.y - tex_coords.height() * rect.height() / old_height;
}
TextVertex {
top_left: [rect.min.x, rect.min.y, extra.z],
bottom_right: [rect.max.x, rect.max.y],
tex_top_left: [tex_coords.min.x, tex_coords.min.y],
tex_bottom_right: [tex_coords.max.x, tex_coords.max.y],
color: extra.color,
}
}
pub fn buffer_layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 2,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: std::mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
shader_location: 3,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: std::mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
shader_location: 4,
},
],
}
}
}