use super::screen_size_uniform::ScreenSizeUniform;
use crate::{MeshEncoder, TextureAtlas, pipelines::MaterialEncoder};
pub struct MsdfTextPipeline {
pipeline: wgpu::RenderPipeline,
}
impl MsdfTextPipeline {
pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
Self::new_and_modify(device, surface_format, |_| {})
}
pub fn new_and_modify(
device: &wgpu::Device,
surface_format: wgpu::TextureFormat,
modifer: impl FnOnce(&mut wgpu::RenderPipelineDescriptor<'_>),
) -> Self {
let shader = device.create_shader_module(wgpu::include_wgsl!("msdf.wgsl"));
let atlas_bgl = TextureAtlas::bind_group_layout(device);
let uniform_bgl = ScreenSizeUniform::bind_group_layout(device);
let storage_bgl = MaterialEncoder::storage_bind_group_layout(device);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("klyff text pipeline layout"),
bind_group_layouts: &[&atlas_bgl, &uniform_bgl, &storage_bgl],
push_constant_ranges: &[],
});
let mut desc = wgpu::RenderPipelineDescriptor {
label: Some("klyff msdf pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
MeshEncoder::msdf_vertex_buffer_layout(),
MaterialEncoder::material_vertex_buffer_layout(),
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::all(),
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
};
modifer(&mut desc);
Self {
pipeline: device.create_render_pipeline(&desc),
}
}
pub fn render<'a>(
&'a self,
pass: &mut wgpu::RenderPass<'a>,
atlas: &'a TextureAtlas,
uniform: &'a ScreenSizeUniform,
mesh: &'a MeshEncoder,
material: &'a MaterialEncoder,
) {
if mesh.msdf_index_count() == 0 {
return;
}
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, atlas.bind_group(), &[]);
pass.set_bind_group(1, uniform.uniform_bind_group(), &[]);
pass.set_bind_group(2, material.storage_bind_group(), &[]);
pass.set_vertex_buffer(0, mesh.msdf_vertex_buffer().slice(..));
pass.set_vertex_buffer(1, material.material_vertex_buffer().slice(..));
pass.set_index_buffer(
mesh.msdf_index_buffer().slice(..),
wgpu::IndexFormat::Uint16,
);
pass.draw_indexed(0..mesh.msdf_index_count(), 0, 0..1);
}
}
pub struct RasterizedTextPipeline(wgpu::RenderPipeline);
impl RasterizedTextPipeline {
pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
Self::new_and_modify(device, surface_format, |_| {})
}
pub fn new_and_modify(
device: &wgpu::Device,
surface_format: wgpu::TextureFormat,
modify: impl FnOnce(&mut wgpu::RenderPipelineDescriptor),
) -> Self {
let shader = device.create_shader_module(wgpu::include_wgsl!("rasterized.wgsl"));
let atlas_bgl = TextureAtlas::bind_group_layout(device);
let uniform_bgl = ScreenSizeUniform::bind_group_layout(device);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("klyff text pipeline layout for rasterized glyphs"),
bind_group_layouts: &[&atlas_bgl, &uniform_bgl],
push_constant_ranges: &[],
});
let mut desc = wgpu::RenderPipelineDescriptor {
label: Some("klyff text pipeline for rasterized glyphs"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[MeshEncoder::rasterized_vertex_buffer_layout()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
};
modify(&mut desc);
Self(device.create_render_pipeline(&desc))
}
pub fn render<'a>(
&'a self,
pass: &mut wgpu::RenderPass<'a>,
atlas: &'a TextureAtlas,
uniform: &'a ScreenSizeUniform,
mesh: &'a MeshEncoder,
) {
if mesh.rasterized_index_count() == 0 {
return;
}
pass.set_pipeline(&self.0);
pass.set_bind_group(0, atlas.bind_group(), &[]);
pass.set_bind_group(1, uniform.uniform_bind_group(), &[]);
pass.set_vertex_buffer(0, mesh.rasterized_vertex_buffer().slice(..));
pass.set_index_buffer(
mesh.rasterized_index_buffer().slice(..),
wgpu::IndexFormat::Uint16,
);
pass.draw_indexed(0..mesh.rasterized_index_count(), 0, 0..1);
}
}
#[cfg(test)]
mod tests {
use super::*;
async fn request_device() -> Option<(wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::None,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok()?;
adapter
.request_device(&wgpu::DeviceDescriptor::default())
.await
.ok()
}
#[test]
fn msdf_pipeline_compiles() {
let Some((device, _)) = pollster::block_on(request_device()) else {
return;
};
MsdfTextPipeline::new(&device, wgpu::TextureFormat::Bgra8UnormSrgb);
}
#[test]
fn rasterized_pipeline_compiles() {
let Some((device, _)) = pollster::block_on(request_device()) else {
return;
};
RasterizedTextPipeline::new(&device, wgpu::TextureFormat::Bgra8UnormSrgb);
}
}