Skip to main content

sable_gpu/
context.rs

1//! GPU context and device management.
2//!
3//! The [`GpuContext`] is the main entry point for GPU operations.
4
5use std::sync::Arc;
6
7use sable_platform::prelude::Window;
8use tracing::{debug, info};
9use wgpu::{
10    Adapter, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits, PresentMode,
11    Queue, Surface, SurfaceCapabilities, SurfaceConfiguration, TextureFormat, TextureUsages,
12};
13
14use crate::Result;
15
16/// The main GPU context holding the instance, adapter, device, queue, and surface.
17pub struct GpuContext {
18    #[allow(dead_code)]
19    instance: Instance,
20    adapter: Adapter,
21    device: Arc<Device>,
22    queue: Arc<Queue>,
23    surface: Surface<'static>,
24    surface_config: SurfaceConfiguration,
25    #[allow(dead_code)]
26    surface_capabilities: SurfaceCapabilities,
27}
28
29impl GpuContext {
30    /// Create a new GPU context for the given window.
31    ///
32    /// This is an async operation as it requires requesting the GPU adapter and device.
33    ///
34    /// # Errors
35    ///
36    /// Returns an error if:
37    /// - No suitable GPU adapter could be found
38    /// - The GPU device could not be requested
39    /// - The surface could not be created
40    pub async fn new(window: &Window) -> Result<Self> {
41        // Create instance with default backends
42        let instance = Instance::new(&InstanceDescriptor {
43            backends: wgpu::Backends::all(),
44            ..Default::default()
45        });
46
47        // Create surface from window
48        // SAFETY: The window handle is valid for the lifetime of the window
49        let surface = unsafe {
50            let target = wgpu::SurfaceTargetUnsafe::from_window(&window.winit_window())?;
51            instance.create_surface_unsafe(target)?
52        };
53
54        // Request adapter
55        let adapter = instance
56            .request_adapter(&wgpu::RequestAdapterOptions {
57                power_preference: wgpu::PowerPreference::HighPerformance,
58                force_fallback_adapter: false,
59                compatible_surface: Some(&surface),
60            })
61            .await?;
62
63        info!(
64            "Selected GPU adapter: {} ({:?})",
65            adapter.get_info().name,
66            adapter.get_info().backend
67        );
68        debug!("Adapter features: {:?}", adapter.features());
69        debug!("Adapter limits: {:?}", adapter.limits());
70
71        // Request device and queue
72        let (device, queue) = adapter
73            .request_device(&DeviceDescriptor {
74                label: Some("Sable GPU Device"),
75                required_features: Features::empty(),
76                required_limits: Limits::default(),
77                memory_hints: wgpu::MemoryHints::Performance,
78                trace: wgpu::Trace::Off,
79            })
80            .await?;
81
82        let device = Arc::new(device);
83        let queue = Arc::new(queue);
84
85        // Get surface capabilities
86        let surface_capabilities = surface.get_capabilities(&adapter);
87        let surface_format = surface_capabilities
88            .formats
89            .iter()
90            .find(|f| f.is_srgb())
91            .copied()
92            .unwrap_or(surface_capabilities.formats[0]);
93
94        debug!("Surface format: {:?}", surface_format);
95        debug!(
96            "Available present modes: {:?}",
97            surface_capabilities.present_modes
98        );
99
100        // Configure surface
101        let size = window.inner_size();
102        let present_mode = if window.vsync() {
103            if surface_capabilities
104                .present_modes
105                .contains(&PresentMode::Fifo)
106            {
107                PresentMode::Fifo
108            } else {
109                surface_capabilities.present_modes[0]
110            }
111        } else if surface_capabilities
112            .present_modes
113            .contains(&PresentMode::Immediate)
114        {
115            PresentMode::Immediate
116        } else if surface_capabilities
117            .present_modes
118            .contains(&PresentMode::Mailbox)
119        {
120            PresentMode::Mailbox
121        } else {
122            surface_capabilities.present_modes[0]
123        };
124
125        let surface_config = SurfaceConfiguration {
126            usage: TextureUsages::RENDER_ATTACHMENT,
127            format: surface_format,
128            width: size.width.max(1),
129            height: size.height.max(1),
130            present_mode,
131            alpha_mode: surface_capabilities.alpha_modes[0],
132            view_formats: vec![],
133            desired_maximum_frame_latency: 2,
134        };
135
136        surface.configure(&device, &surface_config);
137
138        Ok(Self {
139            instance,
140            adapter,
141            device,
142            queue,
143            surface,
144            surface_config,
145            surface_capabilities,
146        })
147    }
148
149    /// Resize the surface to a new size.
150    ///
151    /// Call this when the window is resized.
152    pub fn resize(&mut self, width: u32, height: u32) {
153        if width > 0 && height > 0 {
154            self.surface_config.width = width;
155            self.surface_config.height = height;
156            self.surface.configure(&self.device, &self.surface_config);
157        }
158    }
159
160    /// Get the current surface texture for rendering.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if the surface texture could not be acquired.
165    pub fn get_current_texture(&self) -> Result<wgpu::SurfaceTexture> {
166        Ok(self.surface.get_current_texture()?)
167    }
168
169    /// Get a reference to the GPU device.
170    #[must_use]
171    pub fn device(&self) -> &Arc<Device> {
172        &self.device
173    }
174
175    /// Get a reference to the GPU queue.
176    #[must_use]
177    pub fn queue(&self) -> &Arc<Queue> {
178        &self.queue
179    }
180
181    /// Get the surface texture format.
182    #[must_use]
183    pub fn surface_format(&self) -> TextureFormat {
184        self.surface_config.format
185    }
186
187    /// Get the current surface size.
188    #[must_use]
189    pub fn surface_size(&self) -> (u32, u32) {
190        (self.surface_config.width, self.surface_config.height)
191    }
192
193    /// Get the adapter info.
194    #[must_use]
195    pub fn adapter_info(&self) -> wgpu::AdapterInfo {
196        self.adapter.get_info()
197    }
198
199    /// Create a command encoder for recording GPU commands.
200    #[must_use]
201    pub fn create_command_encoder(&self) -> wgpu::CommandEncoder {
202        self.device
203            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
204                label: Some("Sable Command Encoder"),
205            })
206    }
207
208    /// Submit command buffers to the GPU queue.
209    pub fn submit<I: IntoIterator<Item = wgpu::CommandBuffer>>(&self, commands: I) {
210        self.queue.submit(commands);
211    }
212}