← Build a viewer

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