src/workshop_surface.rs
Repository source snapshot · Plain text · GitHub
1//! Shared presentation policy. The hosts provide a surface and input; this type2//! owns the GPU resources. Offscreen callers can use WorkshopPass directly.3use crate::workshop::{Frame, WorkshopPass};45pub struct WorkshopSurface {6 surface: wgpu::Surface<'static>,7 pub device: wgpu::Device,8 queue: wgpu::Queue,9 config: wgpu::SurfaceConfiguration,10 pub pass: WorkshopPass,11 pub adapter_label: String,12 relight: Option<crate::relight::RelightPass>,13}1415impl WorkshopSurface {16 // ANCHOR: init17 pub async fn new(18 instance: wgpu::Instance,19 surface: wgpu::Surface<'static>,20 width: u32,21 height: u32,22 ) -> Result<Self, String> {23 let adapter = instance24 .request_adapter(&wgpu::RequestAdapterOptions {25 power_preference: wgpu::PowerPreference::HighPerformance,26 compatible_surface: Some(&surface),27 force_fallback_adapter: false,28 apply_limit_buckets: false,29 })30 .await31 .map_err(|e| e.to_string())?;32 let (device, queue) = adapter33 .request_device(&wgpu::DeviceDescriptor::default())34 .await35 .map_err(|e| e.to_string())?;36 let caps = surface.get_capabilities(&adapter);37 // Keep the educational RGB values in the same UNORM convention as the38 // research baseline. A production HDR renderer needs an explicit conversion.39 let format = caps40 .formats41 .iter()42 .copied()43 .find(|f| !f.is_srgb())44 .unwrap_or(caps.formats[0]);45 let config = wgpu::SurfaceConfiguration {46 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,47 format,48 color_space: wgpu::SurfaceColorSpace::Auto,49 width: 1,50 height: 1,51 present_mode: wgpu::PresentMode::Fifo,52 alpha_mode: caps.alpha_modes[0],53 view_formats: vec![],54 desired_maximum_frame_latency: 2,55 };56 let pass = WorkshopPass::new(&device, format);57 let info = adapter.get_info();58 let adapter_label = format!("{:?} · {}", info.backend, info.name);59 let mut renderer = Self {60 surface,61 device,62 queue,63 config,64 pass,65 adapter_label,66 relight: None,67 };68 renderer.resize(width.max(1), height.max(1));69 Ok(renderer)70 }71 // END: init7273 pub fn resize(&mut self, width: u32, height: u32) {74 if width == 0 || height == 0 {75 return;76 }77 let limit = self.device.limits().max_texture_dimension_2d;78 self.config.width = width.min(limit);79 self.config.height = height.min(limit);80 self.surface.configure(&self.device, &self.config);81 }8283 // ANCHOR: frame84 pub fn render(&mut self, input: Frame) -> Result<Option<u32>, String> {85 let frame = match self.surface.get_current_texture() {86 wgpu::CurrentSurfaceTexture::Success(frame)87 | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,88 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {89 self.surface.configure(&self.device, &self.config);90 return Ok(None);91 }92 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {93 return Ok(None)94 }95 wgpu::CurrentSurfaceTexture::Validation => {96 return Err(97 "Surface validation failed; reload to recreate the GPU resources".into(),98 )99 }100 };101 let count =102 self.pass103 .prepare(&self.queue, input, [self.config.width, self.config.height])?;104 let target = frame.texture.create_view(&Default::default());105 let mut encoder = self.device.create_command_encoder(&Default::default());106 {107 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {108 label: Some("workshop-frame"),109 color_attachments: &[Some(wgpu::RenderPassColorAttachment {110 view: &target,111 resolve_target: None,112 depth_slice: None,113 ops: wgpu::Operations {114 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),115 store: wgpu::StoreOp::Store,116 },117 })],118 depth_stencil_attachment: None,119 timestamp_writes: None,120 occlusion_query_set: None,121 multiview_mask: None,122 });123 self.pass.draw(&mut pass);124 }125 self.queue.submit([encoder.finish()]);126 self.queue.present(frame);127 Ok(Some(count))128 }129 // END: frame130131 pub fn render_relight(&mut self, input: crate::relight::RelightFrame) -> Result<bool, String> {132 input.validate()?;133 let frame = match self.surface.get_current_texture() {134 wgpu::CurrentSurfaceTexture::Success(frame)135 | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,136 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {137 self.surface.configure(&self.device, &self.config);138 return Ok(false);139 }140 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {141 return Ok(false)142 }143 wgpu::CurrentSurfaceTexture::Validation => {144 return Err("Relight surface validation failed; reload the lab".into())145 }146 };147 let renderer = self.relight.get_or_insert_with(|| {148 crate::relight::RelightPass::new(&self.device, self.config.format)149 });150 let target = frame.texture.create_view(&Default::default());151 let mut encoder = self.device.create_command_encoder(&Default::default());152 renderer.render(153 &self.device,154 &self.queue,155 &mut encoder,156 &target,157 [self.config.width, self.config.height],158 input,159 )?;160 self.queue.submit([encoder.finish()]);161 self.queue.present(frame);162 Ok(true)163 }164}165