Another project
0

Configure Feed

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

at main 9.3 kB View raw
1use crate::camera::ViewportExtent; 2use crate::gpu::{Capabilities, Gpu}; 3use crate::pick::{PickIndex, Picker}; 4 5const PICK_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint; 6 7#[derive(Debug, thiserror::Error)] 8pub enum SurfaceError { 9 #[error("surface creation: {0}")] 10 Create(#[from] wgpu::CreateSurfaceError), 11 #[error("no wgpu adapter matched the surface: {0}")] 12 NoAdapter(#[from] wgpu::RequestAdapterError), 13 #[error("wgpu device request failed: {0}")] 14 Device(#[from] wgpu::RequestDeviceError), 15 #[error( 16 "surface exposes no srgb color format; baseline requires one. available: {available:?}" 17 )] 18 NoSrgbFormat { available: Vec<wgpu::TextureFormat> }, 19 #[error("viewport dimension is zero")] 20 ZeroExtent, 21} 22 23pub struct SurfaceContext { 24 gpu: Gpu, 25 surface: wgpu::Surface<'static>, 26 config: wgpu::SurfaceConfiguration, 27 pick: wgpu::Texture, 28 pick_staging: wgpu::Buffer, 29 depth: wgpu::Texture, 30 extent: ViewportExtent, 31 reconfigure_pending: bool, 32} 33 34enum AcquireOutcome { 35 Frame(wgpu::SurfaceTexture), 36 Skip, 37 Reconfigure, 38} 39 40impl SurfaceContext { 41 pub async fn new( 42 target: impl Into<wgpu::SurfaceTarget<'static>>, 43 extent: ViewportExtent, 44 ) -> Result<Self, SurfaceError> { 45 if extent.width().value() == 0 || extent.height().value() == 0 { 46 return Err(SurfaceError::ZeroExtent); 47 } 48 let instance = 49 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()); 50 let target: wgpu::SurfaceTarget<'static> = target.into(); 51 let surface = instance.create_surface(target)?; 52 let adapter = instance 53 .request_adapter(&wgpu::RequestAdapterOptions { 54 power_preference: wgpu::PowerPreference::LowPower, 55 force_fallback_adapter: false, 56 compatible_surface: Some(&surface), 57 }) 58 .await?; 59 let (device, queue) = adapter 60 .request_device(&wgpu::DeviceDescriptor { 61 label: Some("bone-render:surface"), 62 required_features: wgpu::Features::empty(), 63 required_limits: wgpu::Limits::downlevel_defaults(), 64 experimental_features: wgpu::ExperimentalFeatures::default(), 65 memory_hints: wgpu::MemoryHints::default(), 66 trace: wgpu::Trace::Off, 67 }) 68 .await?; 69 let capabilities = Capabilities::probe(&adapter); 70 let caps = surface.get_capabilities(&adapter); 71 let Some(format) = caps 72 .formats 73 .iter() 74 .copied() 75 .find(wgpu::TextureFormat::is_srgb) 76 else { 77 return Err(SurfaceError::NoSrgbFormat { 78 available: caps.formats.clone(), 79 }); 80 }; 81 let present_mode = pick_present_mode(&caps.present_modes); 82 let alpha_mode = caps 83 .alpha_modes 84 .first() 85 .copied() 86 .unwrap_or(wgpu::CompositeAlphaMode::Auto); 87 let config = wgpu::SurfaceConfiguration { 88 usage: wgpu::TextureUsages::RENDER_ATTACHMENT, 89 format, 90 width: extent.width().value(), 91 height: extent.height().value(), 92 present_mode, 93 alpha_mode, 94 view_formats: vec![], 95 desired_maximum_frame_latency: 1, 96 }; 97 surface.configure(&device, &config); 98 let pick = create_pick_texture(&device, extent); 99 let pick_staging = device.create_buffer(&wgpu::BufferDescriptor { 100 label: Some("bone-render:surface-pick-readback"), 101 size: u64::from(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT), 102 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, 103 mapped_at_creation: false, 104 }); 105 let depth = crate::depth_texture(&device, extent); 106 Ok(Self { 107 gpu: Gpu::from_parts(device, queue, capabilities), 108 surface, 109 config, 110 pick, 111 pick_staging, 112 depth, 113 extent, 114 reconfigure_pending: false, 115 }) 116 } 117 118 #[must_use] 119 pub fn gpu(&self) -> &Gpu { 120 &self.gpu 121 } 122 123 #[must_use] 124 pub const fn extent(&self) -> ViewportExtent { 125 self.extent 126 } 127 128 #[must_use] 129 pub const fn color_format(&self) -> wgpu::TextureFormat { 130 self.config.format 131 } 132 133 #[must_use] 134 pub fn picker(&self, index: PickIndex) -> Picker<'_> { 135 Picker::new( 136 &self.gpu, 137 &self.pick, 138 &self.pick_staging, 139 self.extent, 140 index, 141 ) 142 } 143 144 pub fn resize(&mut self, extent: ViewportExtent) { 145 if extent.width().value() == 0 || extent.height().value() == 0 { 146 return; 147 } 148 self.extent = extent; 149 self.config.width = extent.width().value(); 150 self.config.height = extent.height().value(); 151 self.surface.configure(self.gpu.device(), &self.config); 152 self.pick = create_pick_texture(self.gpu.device(), extent); 153 self.depth = crate::depth_texture(self.gpu.device(), extent); 154 self.reconfigure_pending = false; 155 } 156 157 pub fn render<F, G>(&mut self, build_passes: F, pre_present: G) 158 where 159 F: FnOnce( 160 &mut wgpu::CommandEncoder, 161 &wgpu::TextureView, 162 &wgpu::TextureView, 163 &wgpu::TextureView, 164 ), 165 G: FnOnce(), 166 { 167 let Some(frame) = self.acquire_frame() else { 168 return; 169 }; 170 let color_view = frame 171 .texture 172 .create_view(&wgpu::TextureViewDescriptor::default()); 173 let pick_view = self 174 .pick 175 .create_view(&wgpu::TextureViewDescriptor::default()); 176 let depth_view = self 177 .depth 178 .create_view(&wgpu::TextureViewDescriptor::default()); 179 let mut encoder = 180 self.gpu 181 .device() 182 .create_command_encoder(&wgpu::CommandEncoderDescriptor { 183 label: Some("bone-render:surface-encoder"), 184 }); 185 build_passes(&mut encoder, &color_view, &pick_view, &depth_view); 186 self.gpu.queue().submit(Some(encoder.finish())); 187 pre_present(); 188 frame.present(); 189 } 190 191 fn acquire_frame(&mut self) -> Option<wgpu::SurfaceTexture> { 192 if self.reconfigure_pending { 193 self.surface.configure(self.gpu.device(), &self.config); 194 self.reconfigure_pending = false; 195 } 196 match self.classify_acquire() { 197 AcquireOutcome::Frame(frame) => Some(frame), 198 AcquireOutcome::Skip => None, 199 AcquireOutcome::Reconfigure => { 200 self.surface.configure(self.gpu.device(), &self.config); 201 match self.classify_acquire() { 202 AcquireOutcome::Frame(frame) => Some(frame), 203 AcquireOutcome::Skip => None, 204 AcquireOutcome::Reconfigure => { 205 tracing::warn!( 206 "surface still outdated/lost after reconfigure; skipping frame" 207 ); 208 None 209 } 210 } 211 } 212 } 213 } 214 215 fn classify_acquire(&mut self) -> AcquireOutcome { 216 match self.surface.get_current_texture() { 217 wgpu::CurrentSurfaceTexture::Success(frame) => AcquireOutcome::Frame(frame), 218 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { 219 self.reconfigure_pending = true; 220 AcquireOutcome::Frame(frame) 221 } 222 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => { 223 AcquireOutcome::Reconfigure 224 } 225 wgpu::CurrentSurfaceTexture::Timeout => { 226 tracing::warn!("surface frame acquire timed out; skipping frame"); 227 AcquireOutcome::Skip 228 } 229 wgpu::CurrentSurfaceTexture::Occluded => { 230 tracing::debug!("surface occluded; skipping frame"); 231 AcquireOutcome::Skip 232 } 233 wgpu::CurrentSurfaceTexture::Validation => { 234 tracing::warn!("surface validation error on acquire; skipping frame"); 235 AcquireOutcome::Skip 236 } 237 } 238 } 239} 240 241fn create_pick_texture(device: &wgpu::Device, extent: ViewportExtent) -> wgpu::Texture { 242 device.create_texture(&wgpu::TextureDescriptor { 243 label: Some("bone-render:surface-pick"), 244 size: wgpu::Extent3d { 245 width: extent.width().value(), 246 height: extent.height().value(), 247 depth_or_array_layers: 1, 248 }, 249 mip_level_count: 1, 250 sample_count: 1, 251 dimension: wgpu::TextureDimension::D2, 252 format: PICK_FORMAT, 253 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, 254 view_formats: &[], 255 }) 256} 257 258fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode { 259 [ 260 wgpu::PresentMode::Mailbox, 261 wgpu::PresentMode::Immediate, 262 wgpu::PresentMode::FifoRelaxed, 263 ] 264 .into_iter() 265 .find(|m| modes.contains(m)) 266 .unwrap_or(wgpu::PresentMode::Fifo) 267}