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