Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 11 kB View raw
1use crate::gpu::Gpu; 2 3const ATLAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm; 4const INITIAL_INSTANCE_CAP: u64 = 256; 5 6#[repr(C, align(16))] 7#[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 8pub struct SdfGlyphInstance { 9 pub rect_xywh_px: [f32; 4], 10 pub uv_min: [f32; 2], 11 pub uv_max: [f32; 2], 12 pub color_premul_rgba: [f32; 4], 13} 14 15const INSTANCE_STRIDE: u64 = core::mem::size_of::<SdfGlyphInstance>() as u64; 16 17const INSTANCE_ATTRS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![ 18 0 => Float32x4, 19 1 => Float32x2, 20 2 => Float32x2, 21 3 => Float32x4, 22]; 23 24#[repr(C, align(16))] 25#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 26struct Frame { 27 viewport_px: [f32; 2], 28 _pad: [f32; 2], 29} 30 31const FRAME_SIZE: u64 = core::mem::size_of::<Frame>() as u64; 32 33pub struct ChromeTextPipeline { 34 device: wgpu::Device, 35 queue: wgpu::Queue, 36 pipeline: wgpu::RenderPipeline, 37 uniform_buffer: wgpu::Buffer, 38 bind_group: wgpu::BindGroup, 39 atlas: wgpu::Texture, 40 atlas_extent: u32, 41 atlas_version: Option<u64>, 42 instance_buffer: wgpu::Buffer, 43 instance_capacity: u64, 44} 45 46impl ChromeTextPipeline { 47 #[must_use] 48 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat, atlas_extent: u32) -> Self { 49 let device = gpu.device().clone(); 50 let queue = gpu.queue().clone(); 51 let atlas = create_atlas_texture(&device, atlas_extent); 52 let atlas_view = atlas.create_view(&wgpu::TextureViewDescriptor::default()); 53 let sampler = device.create_sampler(&wgpu::SamplerDescriptor { 54 label: Some("bone-render:chrome-text-sampler"), 55 address_mode_u: wgpu::AddressMode::ClampToEdge, 56 address_mode_v: wgpu::AddressMode::ClampToEdge, 57 address_mode_w: wgpu::AddressMode::ClampToEdge, 58 mag_filter: wgpu::FilterMode::Linear, 59 min_filter: wgpu::FilterMode::Linear, 60 mipmap_filter: wgpu::MipmapFilterMode::Nearest, 61 ..Default::default() 62 }); 63 let bind_group_layout = create_bind_group_layout(&device); 64 let pipeline = create_pipeline(&device, &bind_group_layout, color_format); 65 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { 66 label: Some("bone-render:chrome-text-uniform"), 67 size: FRAME_SIZE, 68 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 69 mapped_at_creation: false, 70 }); 71 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 72 label: Some("bone-render:chrome-text-bg"), 73 layout: &bind_group_layout, 74 entries: &[ 75 wgpu::BindGroupEntry { 76 binding: 0, 77 resource: uniform_buffer.as_entire_binding(), 78 }, 79 wgpu::BindGroupEntry { 80 binding: 1, 81 resource: wgpu::BindingResource::TextureView(&atlas_view), 82 }, 83 wgpu::BindGroupEntry { 84 binding: 2, 85 resource: wgpu::BindingResource::Sampler(&sampler), 86 }, 87 ], 88 }); 89 let instance_buffer = create_instance_buffer(&device, INITIAL_INSTANCE_CAP); 90 Self { 91 device, 92 queue, 93 pipeline, 94 uniform_buffer, 95 bind_group, 96 atlas, 97 atlas_extent, 98 atlas_version: None, 99 instance_buffer, 100 instance_capacity: INITIAL_INSTANCE_CAP, 101 } 102 } 103 104 pub fn upload( 105 &mut self, 106 viewport_px: [f32; 2], 107 atlas_pixels: &[u8], 108 atlas_version: u64, 109 instances: &[SdfGlyphInstance], 110 ) { 111 if self.atlas_version != Some(atlas_version) { 112 self.upload_atlas(atlas_pixels); 113 self.atlas_version = Some(atlas_version); 114 } 115 let frame = Frame { 116 viewport_px, 117 _pad: [0.0, 0.0], 118 }; 119 self.queue 120 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&frame)); 121 if instances.is_empty() { 122 return; 123 } 124 let needed = instances.len() as u64; 125 if needed > self.instance_capacity { 126 let new_cap = needed.next_power_of_two().max(self.instance_capacity * 2); 127 self.instance_buffer = create_instance_buffer(&self.device, new_cap); 128 self.instance_capacity = new_cap; 129 } 130 self.queue 131 .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(instances)); 132 } 133 134 pub fn draw_range( 135 &self, 136 encoder: &mut wgpu::CommandEncoder, 137 color_view: &wgpu::TextureView, 138 range: core::ops::Range<u32>, 139 ) { 140 if range.start >= range.end { 141 return; 142 } 143 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 144 label: Some("bone-render:chrome-text-pass"), 145 color_attachments: &[Some(wgpu::RenderPassColorAttachment { 146 view: color_view, 147 resolve_target: None, 148 depth_slice: None, 149 ops: wgpu::Operations { 150 load: wgpu::LoadOp::Load, 151 store: wgpu::StoreOp::Store, 152 }, 153 })], 154 depth_stencil_attachment: None, 155 timestamp_writes: None, 156 occlusion_query_set: None, 157 multiview_mask: None, 158 }); 159 pass.set_pipeline(&self.pipeline); 160 pass.set_bind_group(0, &self.bind_group, &[]); 161 let start_bytes = u64::from(range.start) * INSTANCE_STRIDE; 162 let end_bytes = u64::from(range.end) * INSTANCE_STRIDE; 163 pass.set_vertex_buffer(0, self.instance_buffer.slice(start_bytes..end_bytes)); 164 pass.draw(0..6, 0..(range.end - range.start)); 165 } 166 167 fn upload_atlas(&self, pixels: &[u8]) { 168 let bytes_per_row = self.atlas_extent; 169 self.queue.write_texture( 170 wgpu::TexelCopyTextureInfo { 171 texture: &self.atlas, 172 mip_level: 0, 173 origin: wgpu::Origin3d::ZERO, 174 aspect: wgpu::TextureAspect::All, 175 }, 176 pixels, 177 wgpu::TexelCopyBufferLayout { 178 offset: 0, 179 bytes_per_row: Some(bytes_per_row), 180 rows_per_image: Some(self.atlas_extent), 181 }, 182 wgpu::Extent3d { 183 width: self.atlas_extent, 184 height: self.atlas_extent, 185 depth_or_array_layers: 1, 186 }, 187 ); 188 } 189} 190 191impl core::fmt::Debug for ChromeTextPipeline { 192 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 193 f.debug_struct("ChromeTextPipeline") 194 .field("atlas_extent", &self.atlas_extent) 195 .field("atlas_version", &self.atlas_version) 196 .field("instance_capacity", &self.instance_capacity) 197 .finish_non_exhaustive() 198 } 199} 200 201fn create_atlas_texture(device: &wgpu::Device, extent: u32) -> wgpu::Texture { 202 device.create_texture(&wgpu::TextureDescriptor { 203 label: Some("bone-render:chrome-text-atlas"), 204 size: wgpu::Extent3d { 205 width: extent, 206 height: extent, 207 depth_or_array_layers: 1, 208 }, 209 mip_level_count: 1, 210 sample_count: 1, 211 dimension: wgpu::TextureDimension::D2, 212 format: ATLAS_FORMAT, 213 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, 214 view_formats: &[], 215 }) 216} 217 218fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { 219 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 220 label: Some("bone-render:chrome-text-bgl"), 221 entries: &[ 222 wgpu::BindGroupLayoutEntry { 223 binding: 0, 224 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, 225 ty: wgpu::BindingType::Buffer { 226 ty: wgpu::BufferBindingType::Uniform, 227 has_dynamic_offset: false, 228 min_binding_size: wgpu::BufferSize::new(FRAME_SIZE), 229 }, 230 count: None, 231 }, 232 wgpu::BindGroupLayoutEntry { 233 binding: 1, 234 visibility: wgpu::ShaderStages::FRAGMENT, 235 ty: wgpu::BindingType::Texture { 236 sample_type: wgpu::TextureSampleType::Float { filterable: true }, 237 view_dimension: wgpu::TextureViewDimension::D2, 238 multisampled: false, 239 }, 240 count: None, 241 }, 242 wgpu::BindGroupLayoutEntry { 243 binding: 2, 244 visibility: wgpu::ShaderStages::FRAGMENT, 245 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), 246 count: None, 247 }, 248 ], 249 }) 250} 251 252fn create_pipeline( 253 device: &wgpu::Device, 254 bind_group_layout: &wgpu::BindGroupLayout, 255 color_format: wgpu::TextureFormat, 256) -> wgpu::RenderPipeline { 257 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { 258 label: Some("bone-render:chrome-text-shader"), 259 source: wgpu::ShaderSource::Wgsl(include_str!("chrome_text.wgsl").into()), 260 }); 261 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 262 label: Some("bone-render:chrome-text-layout"), 263 bind_group_layouts: &[Some(bind_group_layout)], 264 immediate_size: 0, 265 }); 266 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 267 label: Some("bone-render:chrome-text-pipeline"), 268 layout: Some(&pipeline_layout), 269 vertex: wgpu::VertexState { 270 module: &shader, 271 entry_point: Some("vs"), 272 compilation_options: wgpu::PipelineCompilationOptions::default(), 273 buffers: &[wgpu::VertexBufferLayout { 274 array_stride: INSTANCE_STRIDE, 275 step_mode: wgpu::VertexStepMode::Instance, 276 attributes: &INSTANCE_ATTRS, 277 }], 278 }, 279 fragment: Some(wgpu::FragmentState { 280 module: &shader, 281 entry_point: Some("fs"), 282 compilation_options: wgpu::PipelineCompilationOptions::default(), 283 targets: &[Some(wgpu::ColorTargetState { 284 format: color_format, 285 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 286 write_mask: wgpu::ColorWrites::ALL, 287 })], 288 }), 289 primitive: wgpu::PrimitiveState { 290 topology: wgpu::PrimitiveTopology::TriangleList, 291 strip_index_format: None, 292 front_face: wgpu::FrontFace::Ccw, 293 cull_mode: None, 294 polygon_mode: wgpu::PolygonMode::Fill, 295 conservative: false, 296 unclipped_depth: false, 297 }, 298 depth_stencil: None, 299 multisample: wgpu::MultisampleState::default(), 300 multiview_mask: None, 301 cache: None, 302 }) 303} 304 305fn create_instance_buffer(device: &wgpu::Device, capacity: u64) -> wgpu::Buffer { 306 device.create_buffer(&wgpu::BufferDescriptor { 307 label: Some("bone-render:chrome-text-instances"), 308 size: capacity * INSTANCE_STRIDE, 309 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, 310 mapped_at_creation: false, 311 }) 312}