Another project
0

Configure Feed

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

1use crate::camera::{ViewportExtent, ViewportPx}; 2use crate::pick::{PickId, PickIndex, Picker}; 3use crate::snapshot::{SnapshotFrame, Style}; 4use crate::{RenderError, Result}; 5 6pub(crate) const COLOR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; 7pub(crate) const PICK_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint; 8pub(crate) const BYTES_PER_PIXEL: u32 = 4; 9pub(crate) const PICK_BYTES_PER_PIXEL: u32 = 4; 10 11#[derive(Copy, Clone, Debug, PartialEq, Eq)] 12pub struct BackendTag(wgpu::Backend); 13 14impl BackendTag { 15 #[cfg(test)] 16 pub(crate) const fn from_backend(backend: wgpu::Backend) -> Self { 17 Self(backend) 18 } 19 20 #[must_use] 21 pub fn backend(self) -> wgpu::Backend { 22 self.0 23 } 24} 25 26impl core::fmt::Display for BackendTag { 27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 28 write!(f, "{:?}", self.0) 29 } 30} 31 32#[derive(Clone, Debug)] 33pub struct Capabilities { 34 adapter_limits: wgpu::Limits, 35 backend: BackendTag, 36 adapter_name: String, 37} 38 39impl Capabilities { 40 pub(crate) fn probe(adapter: &wgpu::Adapter) -> Self { 41 let info = adapter.get_info(); 42 Self { 43 adapter_limits: adapter.limits(), 44 backend: BackendTag(info.backend), 45 adapter_name: info.name, 46 } 47 } 48 49 #[must_use] 50 pub fn adapter_limits(&self) -> &wgpu::Limits { 51 &self.adapter_limits 52 } 53 54 #[must_use] 55 pub fn backend(&self) -> BackendTag { 56 self.backend 57 } 58 59 #[must_use] 60 pub fn adapter_name(&self) -> &str { 61 &self.adapter_name 62 } 63} 64 65pub struct Gpu { 66 device: wgpu::Device, 67 queue: wgpu::Queue, 68 capabilities: Capabilities, 69} 70 71impl Gpu { 72 pub(crate) fn from_parts( 73 device: wgpu::Device, 74 queue: wgpu::Queue, 75 capabilities: Capabilities, 76 ) -> Self { 77 Self { 78 device, 79 queue, 80 capabilities, 81 } 82 } 83 84 #[must_use] 85 pub fn device(&self) -> &wgpu::Device { 86 &self.device 87 } 88 89 #[must_use] 90 pub fn queue(&self) -> &wgpu::Queue { 91 &self.queue 92 } 93 94 #[must_use] 95 pub fn capabilities(&self) -> &Capabilities { 96 &self.capabilities 97 } 98} 99 100pub struct OffscreenContext { 101 gpu: Gpu, 102 color: wgpu::Texture, 103 pick: wgpu::Texture, 104 pick_staging: wgpu::Buffer, 105 extent: ViewportExtent, 106} 107 108impl OffscreenContext { 109 pub async fn new(extent: ViewportExtent) -> Result<Self> { 110 if extent.width().value() == 0 || extent.height().value() == 0 { 111 return Err(RenderError::ZeroExtent); 112 } 113 let instance = 114 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()); 115 let adapter = instance 116 .request_adapter(&wgpu::RequestAdapterOptions { 117 power_preference: wgpu::PowerPreference::LowPower, 118 force_fallback_adapter: false, 119 compatible_surface: None, 120 }) 121 .await?; 122 let (device, queue) = adapter 123 .request_device(&wgpu::DeviceDescriptor { 124 label: Some("bone-render:offscreen"), 125 required_features: wgpu::Features::empty(), 126 required_limits: wgpu::Limits::downlevel_defaults(), 127 experimental_features: wgpu::ExperimentalFeatures::default(), 128 memory_hints: wgpu::MemoryHints::default(), 129 trace: wgpu::Trace::Off, 130 }) 131 .await?; 132 let capabilities = Capabilities::probe(&adapter); 133 let color = device.create_texture(&wgpu::TextureDescriptor { 134 label: Some("bone-render:offscreen-color"), 135 size: texture_size(extent), 136 mip_level_count: 1, 137 sample_count: 1, 138 dimension: wgpu::TextureDimension::D2, 139 format: COLOR_FORMAT, 140 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, 141 view_formats: &[], 142 }); 143 let pick = device.create_texture(&wgpu::TextureDescriptor { 144 label: Some("bone-render:offscreen-pick"), 145 size: texture_size(extent), 146 mip_level_count: 1, 147 sample_count: 1, 148 dimension: wgpu::TextureDimension::D2, 149 format: PICK_FORMAT, 150 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, 151 view_formats: &[], 152 }); 153 let pick_staging = device.create_buffer(&wgpu::BufferDescriptor { 154 label: Some("bone-render:pick-readback"), 155 size: u64::from(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT), 156 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, 157 mapped_at_creation: false, 158 }); 159 Ok(Self { 160 gpu: Gpu { 161 device, 162 queue, 163 capabilities, 164 }, 165 color, 166 pick, 167 pick_staging, 168 extent, 169 }) 170 } 171 172 #[must_use] 173 pub fn gpu(&self) -> &Gpu { 174 &self.gpu 175 } 176 177 #[must_use] 178 pub fn extent(&self) -> ViewportExtent { 179 self.extent 180 } 181 182 #[must_use] 183 pub const fn color_format(&self) -> wgpu::TextureFormat { 184 COLOR_FORMAT 185 } 186 187 #[must_use] 188 pub fn color_view(&self) -> wgpu::TextureView { 189 self.color 190 .create_view(&wgpu::TextureViewDescriptor::default()) 191 } 192 193 #[must_use] 194 pub fn pick_view(&self) -> wgpu::TextureView { 195 self.pick 196 .create_view(&wgpu::TextureViewDescriptor::default()) 197 } 198 199 #[must_use] 200 pub fn picker(&self, index: PickIndex) -> Picker<'_> { 201 Picker::new( 202 &self.gpu, 203 &self.pick, 204 &self.pick_staging, 205 self.extent, 206 index, 207 ) 208 } 209 210 pub fn render_clear(&self, style: &Style) -> Result<SnapshotFrame> { 211 self.render(|encoder, color_view, pick_view| { 212 let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 213 label: Some("bone-render:clear"), 214 color_attachments: &[ 215 Some(wgpu::RenderPassColorAttachment { 216 view: color_view, 217 resolve_target: None, 218 depth_slice: None, 219 ops: wgpu::Operations { 220 load: wgpu::LoadOp::Clear(style.background().into()), 221 store: wgpu::StoreOp::Store, 222 }, 223 }), 224 Some(wgpu::RenderPassColorAttachment { 225 view: pick_view, 226 resolve_target: None, 227 depth_slice: None, 228 ops: wgpu::Operations { 229 load: wgpu::LoadOp::Clear(pick_clear_color()), 230 store: wgpu::StoreOp::Store, 231 }, 232 }), 233 ], 234 depth_stencil_attachment: None, 235 timestamp_writes: None, 236 occlusion_query_set: None, 237 multiview_mask: None, 238 }); 239 }) 240 } 241 242 pub(crate) fn render<F>(&self, mut build_passes: F) -> Result<SnapshotFrame> 243 where 244 F: FnMut(&mut wgpu::CommandEncoder, &wgpu::TextureView, &wgpu::TextureView), 245 { 246 let mut encoder = self 247 .gpu 248 .device 249 .create_command_encoder(&wgpu::CommandEncoderDescriptor { 250 label: Some("bone-render:encoder"), 251 }); 252 let color_view = self.color_view(); 253 let pick_view = self.pick_view(); 254 build_passes(&mut encoder, &color_view, &pick_view); 255 let padded_bpr = padded_bytes_per_row(self.extent.width()); 256 let staging = self.gpu.device.create_buffer(&wgpu::BufferDescriptor { 257 label: Some("bone-render:readback"), 258 size: u64::from(padded_bpr) * u64::from(self.extent.height().value()), 259 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, 260 mapped_at_creation: false, 261 }); 262 encoder.copy_texture_to_buffer( 263 wgpu::TexelCopyTextureInfo { 264 texture: &self.color, 265 mip_level: 0, 266 origin: wgpu::Origin3d::ZERO, 267 aspect: wgpu::TextureAspect::All, 268 }, 269 wgpu::TexelCopyBufferInfo { 270 buffer: &staging, 271 layout: wgpu::TexelCopyBufferLayout { 272 offset: 0, 273 bytes_per_row: Some(padded_bpr), 274 rows_per_image: Some(self.extent.height().value()), 275 }, 276 }, 277 texture_size(self.extent), 278 ); 279 self.gpu.queue.submit(Some(encoder.finish())); 280 let rgba = read_staging(&self.gpu.device, &staging, self.extent, padded_bpr)?; 281 Ok(SnapshotFrame::new( 282 self.extent, 283 rgba, 284 self.gpu.capabilities.backend(), 285 )) 286 } 287} 288 289pub(crate) fn clear_pick_attachment( 290 encoder: &mut wgpu::CommandEncoder, 291 pick_view: &wgpu::TextureView, 292) { 293 let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 294 label: Some("bone-render:pick-clear"), 295 color_attachments: &[Some(wgpu::RenderPassColorAttachment { 296 view: pick_view, 297 resolve_target: None, 298 depth_slice: None, 299 ops: wgpu::Operations { 300 load: wgpu::LoadOp::Clear(pick_clear_color()), 301 store: wgpu::StoreOp::Store, 302 }, 303 })], 304 depth_stencil_attachment: None, 305 timestamp_writes: None, 306 occlusion_query_set: None, 307 multiview_mask: None, 308 }); 309} 310 311fn pick_clear_color() -> wgpu::Color { 312 wgpu::Color { 313 r: f64::from(PickId::NONE.raw()), 314 g: 0.0, 315 b: 0.0, 316 a: 0.0, 317 } 318} 319 320fn texture_size(extent: ViewportExtent) -> wgpu::Extent3d { 321 wgpu::Extent3d { 322 width: extent.width().value(), 323 height: extent.height().value(), 324 depth_or_array_layers: 1, 325 } 326} 327 328pub(crate) fn padded_bytes_per_row(width: ViewportPx) -> u32 { 329 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; 330 let w = width.value(); 331 debug_assert!( 332 w <= u32::MAX / BYTES_PER_PIXEL, 333 "viewport width too large for padded row calc" 334 ); 335 let raw = w * BYTES_PER_PIXEL; 336 raw.div_ceil(align) * align 337} 338 339fn read_staging( 340 device: &wgpu::Device, 341 buffer: &wgpu::Buffer, 342 extent: ViewportExtent, 343 padded_bpr: u32, 344) -> Result<Vec<u8>> { 345 let slice = buffer.slice(..); 346 let (tx, rx) = 347 std::sync::mpsc::sync_channel::<core::result::Result<(), wgpu::BufferAsyncError>>(1); 348 slice.map_async(wgpu::MapMode::Read, move |res| { 349 let _ = tx.send(res); 350 }); 351 device 352 .poll(wgpu::PollType::wait_indefinitely()) 353 .map_err(RenderError::Poll)?; 354 match rx.try_recv() { 355 Ok(Ok(())) => {} 356 Ok(Err(e)) => return Err(RenderError::Map(e)), 357 Err(_) => return Err(RenderError::MapMissing), 358 } 359 let row_bytes = extent.width().value() * BYTES_PER_PIXEL; 360 let rgba: Vec<u8> = { 361 let view = slice.get_mapped_range(); 362 (0..extent.height().value()) 363 .flat_map(|y| { 364 let start = (y * padded_bpr) as usize; 365 let end = start + row_bytes as usize; 366 view[start..end].iter().copied() 367 }) 368 .collect() 369 }; 370 buffer.unmap(); 371 Ok(rgba) 372}