//! wgpu rendering context //! //! Manages the wgpu device, queue, and surface for rendering. use std::sync::Arc; use winit::window::Window; /// Rendering context holding wgpu state pub struct RenderContext { pub surface: wgpu::Surface<'static>, pub device: wgpu::Device, pub queue: wgpu::Queue, pub config: wgpu::SurfaceConfiguration, pub size: winit::dpi::PhysicalSize, // Keep window alive _window: Arc, } impl RenderContext { /// Create a new render context from a window with VSync enabled pub async fn new(window: Arc) -> Self { Self::with_vsync(window, true).await } /// Create a new render context from a window with configurable VSync pub async fn with_vsync(window: Arc, vsync: bool) -> Self { let size = window.inner_size(); // Create wgpu instance let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() }); // Create surface let surface = instance.create_surface(window.clone()).unwrap(); // Request adapter let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .expect("Failed to find an appropriate adapter"); // Request device let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: Some("Rust4D Device"), required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::default(), memory_hints: wgpu::MemoryHints::default(), trace: wgpu::Trace::Off, }) .await .expect("Failed to create device"); // Configure surface let surface_caps = surface.get_capabilities(&adapter); let surface_format = surface_caps .formats .iter() .copied() .find(|f| f.is_srgb()) .unwrap_or(surface_caps.formats[0]); let present_mode = if vsync { wgpu::PresentMode::AutoVsync } else { wgpu::PresentMode::AutoNoVsync }; let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: size.width, height: size.height, present_mode, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, }; surface.configure(&device, &config); Self { surface, device, queue, config, size, _window: window, } } /// Handle window resize pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); } } /// Render a frame with a clear color pub fn render_clear(&self, color: wgpu::Color) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output .texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Clear Encoder"), }); { let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Clear Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(color), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, }); } self.queue.submit(std::iter::once(encoder.finish())); output.present(); Ok(()) } /// Get the aspect ratio pub fn aspect_ratio(&self) -> f32 { self.size.width as f32 / self.size.height as f32 } }