orbit/renderer/wgpu/
mod.rs

1//! WGPU renderer base functionality
2
3#[cfg(feature = "wgpu")]
4use std::sync::Arc;
5#[cfg(feature = "wgpu")]
6use wgpu::{Adapter, Device, Instance, Queue, Surface, SurfaceConfiguration};
7
8#[cfg(feature = "wgpu")]
9use crate::component::Node;
10#[cfg(feature = "wgpu")]
11use crate::renderer::{RenderContext, Renderer};
12#[cfg(feature = "wgpu")]
13use crate::Error;
14
15/// WGPU renderer for 3D rendering
16#[cfg(feature = "wgpu")]
17pub struct WgpuRenderer {
18    /// WGPU instance
19    #[allow(dead_code)]
20    instance: Instance,
21
22    /// WGPU device
23    device: Arc<Device>,
24
25    /// WGPU queue
26    queue: Arc<Queue>,
27
28    /// WGPU surface
29    surface: Option<Surface<'static>>,
30
31    /// Surface configuration
32    surface_config: Option<SurfaceConfiguration>,
33
34    /// WGPU adapter
35    adapter: Adapter,
36}
37
38#[cfg(feature = "wgpu")]
39impl WgpuRenderer {
40    /// Create a new WGPU renderer
41    pub async fn new() -> Result<Self, Error> {
42        // Create WGPU instance
43        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
44            backends: wgpu::Backends::all(),
45            flags: wgpu::InstanceFlags::default(),
46            dx12_shader_compiler: wgpu::Dx12Compiler::default(),
47            gles_minor_version: wgpu::Gles3MinorVersion::default(),
48        });
49
50        // Select adapter
51        let adapter = instance
52            .request_adapter(&wgpu::RequestAdapterOptions {
53                power_preference: wgpu::PowerPreference::HighPerformance,
54                compatible_surface: None,
55                force_fallback_adapter: false,
56            })
57            .await
58            .ok_or_else(|| Error::Renderer("Failed to find GPU adapter".to_string()))?;
59
60        // Create device and queue
61        let (device, queue) = adapter
62            .request_device(
63                &wgpu::DeviceDescriptor {
64                    label: Some("Orbit WGPU Device"),
65                    required_features: wgpu::Features::empty(),
66                    required_limits: wgpu::Limits::default(),
67                },
68                None,
69            )
70            .await
71            .map_err(|e| Error::Renderer(format!("Failed to create device: {e}")))?;
72
73        Ok(Self {
74            instance,
75            device: Arc::new(device),
76            queue: Arc::new(queue),
77            surface: None,
78            surface_config: None,
79            adapter,
80        })
81    }
82
83    /// Configure the renderer with a surface
84    pub fn configure_surface(
85        &mut self,
86        surface: Surface<'static>,
87        width: u32,
88        height: u32,
89    ) -> Result<(), Error> {
90        let surface_caps = surface.get_capabilities(&self.adapter);
91        let surface_format = surface_caps
92            .formats
93            .iter()
94            .copied()
95            .find(|f| f.is_srgb())
96            .unwrap_or(surface_caps.formats[0]);
97
98        let config = wgpu::SurfaceConfiguration {
99            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
100            format: surface_format,
101            width,
102            height,
103            present_mode: surface_caps.present_modes[0],
104            alpha_mode: surface_caps.alpha_modes[0],
105            view_formats: vec![],
106            desired_maximum_frame_latency: 2, // Default value for most applications
107        };
108
109        surface.configure(&self.device, &config);
110
111        self.surface = Some(surface);
112        self.surface_config = Some(config);
113
114        Ok(())
115    }
116
117    /// Get a reference to the device
118    pub fn device(&self) -> &Device {
119        &self.device
120    }
121
122    /// Get a reference to the queue
123    pub fn queue(&self) -> &Queue {
124        &self.queue
125    }
126
127    /// Resize the renderer
128    pub fn resize(&mut self, width: u32, height: u32) -> Result<(), Error> {
129        if let Some(surface) = &self.surface {
130            if let Some(config) = &mut self.surface_config {
131                config.width = width;
132                config.height = height;
133                surface.configure(&self.device, config);
134                Ok(())
135            } else {
136                Err(Error::Renderer("Surface not configured".to_string()))
137            }
138        } else {
139            Err(Error::Renderer("Surface not initialized".to_string()))
140        }
141    }
142}
143
144#[cfg(feature = "wgpu")]
145impl Renderer for WgpuRenderer {
146    fn init(&mut self) -> Result<(), crate::Error> {
147        // Nothing to do here as initialization is done in the constructor
148        Ok(())
149    }
150
151    fn render(&mut self, _root: &Node, _context: &mut RenderContext) -> Result<(), Error> {
152        // Get current surface texture
153        if let Some(surface) = &self.surface {
154            let frame = surface
155                .get_current_texture()
156                .map_err(|e| Error::Renderer(format!("Failed to get next frame: {e}")))?;
157
158            let view = frame
159                .texture
160                .create_view(&wgpu::TextureViewDescriptor::default());
161
162            let mut encoder = self
163                .device
164                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
165                    label: Some("Render Encoder"),
166                });
167
168            // Clear the frame
169            {
170                let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
171                    label: Some("Render Pass"),
172                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
173                        view: &view,
174                        resolve_target: None,
175                        ops: wgpu::Operations {
176                            load: wgpu::LoadOp::Clear(wgpu::Color {
177                                r: 0.1,
178                                g: 0.2,
179                                b: 0.3,
180                                a: 1.0,
181                            }),
182                            store: wgpu::StoreOp::Store,
183                        },
184                    })],
185                    depth_stencil_attachment: None,
186                    timestamp_writes: None,
187                    occlusion_query_set: None,
188                });
189
190                // Render components from the node tree would happen here
191            }
192
193            // Submit commands
194            self.queue.submit(std::iter::once(encoder.finish()));
195            frame.present();
196
197            Ok(())
198        } else {
199            Err(Error::Renderer("Surface not initialized".to_string()))
200        }
201    }
202
203    fn flush(&mut self) -> Result<(), Error> {
204        // WGPU already submits and presents in the render method
205        // No additional flushing needed
206        Ok(())
207    }
208
209    fn cleanup(&mut self) -> Result<(), Error> {
210        // Release surface
211        self.surface = None;
212        self.surface_config = None;
213        Ok(())
214    }
215
216    fn name(&self) -> &str {
217        "WGPU Renderer"
218    }
219}