Skip to main content

frus_gpu/
renderer.rs

1//! The renderer bound to a surface (a window): it configures wgpu and delegates
2//! primitive drawing to the [`Painter`].
3
4use frus_core::Scene;
5
6use crate::compositor::{preferred_sample_count, Painters};
7
8/// The background colour, a midnight blue.
9const CLEAR_COLOR: wgpu::Color = wgpu::Color {
10    r: 0.05,
11    g: 0.05,
12    b: 0.08,
13    a: 1.0,
14};
15
16/// Holds the GPU state bound to a surface and presents the frames.
17pub struct Renderer {
18    surface: wgpu::Surface<'static>,
19    device: wgpu::Device,
20    queue: wgpu::Queue,
21    config: wgpu::SurfaceConfiguration,
22    painters: Painters,
23}
24
25impl Renderer {
26    /// Initialises the GPU context for a given surface.
27    ///
28    /// `target` is typically an `Arc<Window>` supplied by the platform layer.
29    /// `width` and `height` must both be > 0.
30    pub async fn new(
31        target: impl Into<wgpu::SurfaceTarget<'static>>,
32        width: u32,
33        height: u32,
34    ) -> anyhow::Result<Self> {
35        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
36            backends: wgpu::Backends::PRIMARY,
37            ..wgpu::InstanceDescriptor::new_without_display_handle()
38        });
39
40        let surface = instance.create_surface(target)?;
41
42        let adapter = instance
43            .request_adapter(&wgpu::RequestAdapterOptions {
44                power_preference: wgpu::PowerPreference::HighPerformance,
45                compatible_surface: Some(&surface),
46                force_fallback_adapter: false,
47                ..Default::default()
48            })
49            .await?;
50
51        log::info!("Adaptateur GPU : {:?}", adapter.get_info());
52
53        // Downlevel limits, for GLES compatibility, but with the adapter's **real**
54        // resolution: on mobile a screen of, say, 1080x2340 exceeds the downlevel
55        // maximum texture size of 2048, and without this `surface.configure` panics.
56        let required_limits = wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits());
57
58        let (device, queue) = adapter
59            .request_device(&wgpu::DeviceDescriptor {
60                label: Some("frus.device"),
61                required_features: wgpu::Features::empty(),
62                required_limits,
63                memory_hints: wgpu::MemoryHints::Performance,
64                ..Default::default()
65            })
66            .await?;
67
68        let caps = surface.get_capabilities(&adapter);
69        let format = caps
70            .formats
71            .iter()
72            .copied()
73            .find(|f| f.is_srgb())
74            .unwrap_or(caps.formats[0]);
75
76        let config = wgpu::SurfaceConfiguration {
77            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
78            format,
79            color_space: wgpu::SurfaceColorSpace::Auto,
80            width,
81            height,
82            present_mode: caps.present_modes[0],
83            alpha_mode: caps.alpha_modes[0],
84            view_formats: vec![],
85            desired_maximum_frame_latency: 2,
86        };
87        surface.configure(&device, &config);
88
89        // MSAA when the adapter supports it for this format; otherwise 1, disabled.
90        let sample_count = preferred_sample_count(&adapter, format);
91        log::info!("MSAA: {sample_count}×");
92
93        let mut painters = Painters::new(&device, &queue, format, sample_count);
94        // Warms every pipeline before the first real frame, to avoid jank.
95        painters.warm_up(&device, &queue, format);
96
97        Ok(Self {
98            surface,
99            device,
100            queue,
101            config,
102            painters,
103        })
104    }
105
106    /// Reconfigures the surface after the window is resized. The painters' viewports
107    /// are set every frame by `Painters::render`.
108    pub fn resize(&mut self, width: u32, height: u32) {
109        if width > 0 && height > 0 {
110            self.config.width = width;
111            self.config.height = height;
112            self.surface.configure(&self.device, &self.config);
113        }
114    }
115
116    /// Reapplies the current configuration, after a lost or outdated surface.
117    pub fn reconfigure(&mut self) {
118        self.surface.configure(&self.device, &self.config);
119    }
120
121    /// Draws the scene — rectangles, images, paths, text, layers — and presents it.
122    pub fn render(&mut self, scene: &Scene) -> RenderOutcome {
123        let frame = match self.surface.get_current_texture() {
124            wgpu::CurrentSurfaceTexture::Success(frame)
125            | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
126            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
127                return RenderOutcome::Skipped;
128            }
129            wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
130                return RenderOutcome::NeedsReconfigure;
131            }
132            wgpu::CurrentSurfaceTexture::Validation => {
133                log::warn!("frame skipped: surface validation error");
134                return RenderOutcome::Skipped;
135            }
136        };
137        let view = frame
138            .texture
139            .create_view(&wgpu::TextureViewDescriptor::default());
140
141        self.painters.render(
142            &self.device,
143            &self.queue,
144            self.config.format,
145            &view,
146            self.config.width,
147            self.config.height,
148            scene,
149            Some(CLEAR_COLOR),
150        );
151
152        self.queue.present(frame);
153        RenderOutcome::Presented
154    }
155}
156
157/// What happened when [`Renderer::render`] tried to present a frame.
158pub enum RenderOutcome {
159    /// The frame was drawn and presented.
160    Presented,
161    /// The frame was skipped (occlusion, timeout, or a validation error); the caller
162    /// should just try again on the next redraw.
163    Skipped,
164    /// The surface is lost or outdated: call [`Renderer::reconfigure`] before retrying.
165    NeedsReconfigure,
166}