Skip to main content

paint_api/
rendering_context.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(unsafe_code)]
6
7use std::cell::{Cell, RefCell, RefMut};
8use std::num::NonZeroU32;
9use std::rc::Rc;
10use std::sync::Arc;
11
12use dpi::PhysicalSize;
13use embedder_traits::RefreshDriver;
14use euclid::default::{Rect, Size2D as UntypedSize2D};
15use euclid::{Point2D, Size2D};
16use gleam::gl::{self, Gl};
17use glow::{HasContext, NativeFramebuffer};
18use image::RgbaImage;
19use log::{debug, trace, warn};
20use raw_window_handle::{DisplayHandle, WindowHandle};
21pub use surfman::Error;
22use surfman::chains::{PreserveBuffer, SwapChain};
23use surfman::{
24    Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
25    GLVersion, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture, SurfaceType,
26};
27use webrender_api::units::{DeviceIntRect, DevicePixel};
28
29/// The `RenderingContext` trait defines a set of methods for managing
30/// an OpenGL or GLES rendering context.
31/// Implementors of this trait are responsible for handling the creation,
32/// management, and destruction of the rendering context and its associated
33/// resources.
34pub trait RenderingContext {
35    /// Prepare this [`RenderingContext`] to be rendered upon by Servo. For instance,
36    /// by binding a framebuffer to the current OpenGL context.
37    fn prepare_for_rendering(&self) {}
38    /// Read the contents of this [`Renderingcontext`] into an in-memory image. If the
39    /// image cannot be read (for instance, if no rendering has taken place yet), then
40    /// `None` is returned.
41    ///
42    /// In a double-buffered [`RenderingContext`] this is expected to read from the back
43    /// buffer. That means that once Servo renders to the context, this should return those
44    /// results, even before [`RenderingContext::present`] is called.
45    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage>;
46    /// Get the current size of this [`RenderingContext`].
47    fn size(&self) -> PhysicalSize<u32>;
48    /// Get the current size of this [`RenderingContext`] as [`Size2D`].
49    fn size2d(&self) -> Size2D<u32, DevicePixel> {
50        let size = self.size();
51        Size2D::new(size.width, size.height)
52    }
53    /// Resizes the rendering surface to the given size.
54    fn resize(&self, size: PhysicalSize<u32>);
55    /// Presents the rendered frame to the screen. In a double-buffered context, this would
56    /// swap buffers.
57    fn present(&self);
58    /// Makes the context the current OpenGL context for this thread.
59    /// After calling this function, it is valid to use OpenGL rendering
60    /// commands.
61    fn make_current(&self) -> Result<(), Error>;
62    /// Returns the `gleam` version of the OpenGL or GLES API.
63    fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
64    /// Returns the OpenGL or GLES API.
65    fn glow_gl_api(&self) -> Arc<glow::Context>;
66    /// Creates a texture from a given surface and returns the surface texture,
67    /// the OpenGL texture object, and the size of the surface. Default to `None`.
68    fn create_texture(
69        &self,
70        _surface: Surface,
71    ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
72        None
73    }
74    /// Destroys the texture and returns the surface. Default to `None`.
75    fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> {
76        None
77    }
78    /// The connection to the display server for WebGL. Default to `None`.
79    fn connection(&self) -> Option<Connection> {
80        None
81    }
82    /// Return the [`RefreshDriver`] for this [`RenderingContext`]. If `None` is returned,
83    /// then the default timer-based [`RefreshDriver`] will be used.
84    fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
85        None
86    }
87}
88
89/// A rendering context that uses the Surfman library to create and manage
90/// the OpenGL context and surface. This struct provides the default implementation
91/// of the `RenderingContext` trait, handling the creation, management, and destruction
92/// of the rendering context and its associated resources.
93///
94/// The `SurfmanRenderingContext` struct encapsulates the necessary data and methods
95/// to interact with the Surfman library, including creating surfaces, binding surfaces,
96/// resizing surfaces, presenting rendered frames, and managing the OpenGL context state.
97struct SurfmanRenderingContext {
98    gleam_gl: Rc<dyn Gl>,
99    glow_gl: Arc<glow::Context>,
100    device: RefCell<Device>,
101    context: RefCell<Context>,
102    refresh_driver: Option<Rc<dyn RefreshDriver>>,
103}
104
105impl Drop for SurfmanRenderingContext {
106    fn drop(&mut self) {
107        let device = &mut self.device.borrow_mut();
108        let context = &mut self.context.borrow_mut();
109        let _ = device.destroy_context(context);
110    }
111}
112
113impl SurfmanRenderingContext {
114    fn new(
115        connection: &Connection,
116        adapter: &Adapter,
117        refresh_driver: Option<Rc<dyn RefreshDriver>>,
118    ) -> Result<Self, Error> {
119        let device = connection.create_device(adapter)?;
120
121        let flags = ContextAttributeFlags::ALPHA |
122            ContextAttributeFlags::DEPTH |
123            ContextAttributeFlags::STENCIL;
124        let gl_api = connection.gl_api();
125        let version = match &gl_api {
126            GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 },
127            GLApi::GL => surfman::GLVersion { major: 3, minor: 2 },
128        };
129        let context_descriptor =
130            device.create_context_descriptor(&ContextAttributes { flags, version })?;
131
132        let mut context = device
133            .create_context(&context_descriptor, None)
134            .inspect_err(|_| {
135                print_diagnostics_information_on_context_creation_failure(&device, gl_api, version)
136            })?;
137
138        // `glow::Context::from_loader_function` immediately queries `GL_VERSION`, so the
139        // context must be current.
140        if let Err(error) = device.make_context_current(&context) {
141            let _ = device.destroy_context(&mut context);
142            return Err(error);
143        }
144
145        #[expect(unsafe_code)]
146        let gleam_gl = {
147            match gl_api {
148                GLApi::GL => unsafe {
149                    gl::GlFns::load_with(|func_name| device.get_proc_address(&context, func_name))
150                },
151                GLApi::GLES => unsafe {
152                    gl::GlesFns::load_with(|func_name| device.get_proc_address(&context, func_name))
153                },
154            }
155        };
156
157        #[expect(unsafe_code)]
158        let glow_gl = unsafe {
159            glow::Context::from_loader_function(|function_name| {
160                device.get_proc_address(&context, function_name)
161            })
162        };
163
164        Ok(SurfmanRenderingContext {
165            gleam_gl,
166            glow_gl: Arc::new(glow_gl),
167            device: RefCell::new(device),
168            context: RefCell::new(context),
169            refresh_driver,
170        })
171    }
172
173    fn create_surface(&self, surface_type: SurfaceType<NativeWidget>) -> Result<Surface, Error> {
174        let device = &mut self.device.borrow_mut();
175        let context = &self.context.borrow();
176        device.create_surface(context, SurfaceAccess::GPUOnly, surface_type)
177    }
178
179    fn bind_surface(&self, surface: Surface) -> Result<(), Error> {
180        let device = &self.device.borrow();
181        let context = &mut self.context.borrow_mut();
182        device
183            .bind_surface_to_context(context, surface)
184            .map_err(|(err, mut surface)| {
185                let _ = device.destroy_surface(context, &mut surface);
186                err
187            })?;
188        Ok(())
189    }
190
191    fn create_attached_swap_chain(&self) -> Result<SwapChain<Device>, Error> {
192        let device = &mut self.device.borrow_mut();
193        let context = &mut self.context.borrow_mut();
194        SwapChain::create_attached(device, context, SurfaceAccess::GPUOnly)
195    }
196
197    fn resize_bound_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> {
198        if size.width == 0 || size.height == 0 {
199            log::error!("Unable to resize to size under 1x1 ({size:?} provided)");
200            return Err(Error::Failed);
201        }
202
203        let size = Size2D::new(size.width as i32, size.height as i32);
204        self.device
205            .borrow()
206            .resize_bound_surface(&mut self.context.borrow_mut(), size)
207    }
208
209    fn present_bound_surface(&self) -> Result<(), Error> {
210        self.device
211            .borrow()
212            .present_bound_surface(&mut self.context.borrow_mut())
213    }
214
215    fn framebuffer(&self) -> Option<NativeFramebuffer> {
216        let device = &self.device.borrow();
217        let context = &self.context.borrow();
218        device
219            .context_surface_info(context)
220            .unwrap_or(None)
221            .and_then(|info| info.framebuffer_object)
222    }
223
224    fn prepare_for_rendering(&self) {
225        let framebuffer_id = self
226            .framebuffer()
227            .map_or(0, |framebuffer| framebuffer.0.into());
228        self.gleam_gl
229            .bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_id);
230    }
231
232    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
233        let framebuffer_id = self
234            .framebuffer()
235            .map_or(0, |framebuffer| framebuffer.0.into());
236        Framebuffer::read_framebuffer_to_image(&self.gleam_gl, framebuffer_id, source_rectangle)
237    }
238
239    fn make_current(&self) -> Result<(), Error> {
240        let device = &self.device.borrow();
241        let context = &mut self.context.borrow();
242        device.make_context_current(context)
243    }
244
245    fn create_texture(
246        &self,
247        surface: Surface,
248    ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
249        let device = &self.device.borrow();
250        let context = &mut self.context.borrow_mut();
251        let SurfaceInfo {
252            id: front_buffer_id,
253            size,
254            ..
255        } = device.surface_info(&surface);
256        debug!("... getting texture for surface {:?}", front_buffer_id);
257        let surface_texture = device.create_surface_texture(context, surface).unwrap();
258        let gl_texture = device
259            .surface_texture_object(&surface_texture)
260            .map(|tex| tex.0.get())
261            .unwrap_or(0);
262        Some((surface_texture, gl_texture, size))
263    }
264
265    fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
266        let device = &self.device.borrow();
267        let context = &mut self.context.borrow_mut();
268        device
269            .destroy_surface_texture(context, surface_texture)
270            .map_err(|(error, _)| error)
271            .ok()
272    }
273
274    fn connection(&self) -> Option<Connection> {
275        Some(self.device.borrow().connection())
276    }
277
278    fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
279        self.refresh_driver.clone()
280    }
281}
282
283/// A software rendering context that uses a software OpenGL implementation to render
284/// Servo. This will generally have bad performance, but can be used in situations where
285/// it is more convenient to have consistent, but slower display output.
286///
287/// The results of the render can be accessed via [`RenderingContext::read_to_image`].
288pub struct SoftwareRenderingContext {
289    size: Cell<PhysicalSize<u32>>,
290    surfman_rendering_info: SurfmanRenderingContext,
291    swap_chain: SwapChain<Device>,
292}
293
294impl SoftwareRenderingContext {
295    pub fn new(size: PhysicalSize<u32>) -> Result<Self, Error> {
296        if size.width == 0 || size.height == 0 {
297            log::error!(
298                "Unable to create SoftwareRenderingContext with size under 1x1 ({size:?} provided)"
299            );
300            return Err(Error::Failed);
301        }
302
303        let connection = Connection::new()?;
304        let adapter = connection.create_software_adapter()?;
305        let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter, None)?;
306
307        let surfman_size = Size2D::new(size.width as i32, size.height as i32);
308        let surface =
309            surfman_rendering_info.create_surface(SurfaceType::Generic { size: surfman_size })?;
310        surfman_rendering_info.bind_surface(surface)?;
311        surfman_rendering_info.make_current()?;
312
313        let swap_chain = surfman_rendering_info.create_attached_swap_chain()?;
314        Ok(SoftwareRenderingContext {
315            size: Cell::new(size),
316            surfman_rendering_info,
317            swap_chain,
318        })
319    }
320}
321
322impl Drop for SoftwareRenderingContext {
323    fn drop(&mut self) {
324        let device = &mut self.surfman_rendering_info.device.borrow_mut();
325        let context = &mut self.surfman_rendering_info.context.borrow_mut();
326        let _ = self.swap_chain.destroy(device, context);
327    }
328}
329
330impl RenderingContext for SoftwareRenderingContext {
331    fn prepare_for_rendering(&self) {
332        self.surfman_rendering_info.prepare_for_rendering();
333    }
334
335    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
336        self.surfman_rendering_info.read_to_image(source_rectangle)
337    }
338
339    fn size(&self) -> PhysicalSize<u32> {
340        self.size.get()
341    }
342
343    fn resize(&self, size: PhysicalSize<u32>) {
344        assert!(
345            size.width > 0 && size.height > 0,
346            "Dimensions must be at least 1x1, got {size:?}",
347        );
348
349        if self.size.get() == size {
350            return;
351        }
352
353        self.size.set(size);
354
355        let device = &mut self.surfman_rendering_info.device.borrow_mut();
356        let context = &mut self.surfman_rendering_info.context.borrow_mut();
357        let size = Size2D::new(size.width as i32, size.height as i32);
358        let _ = self.swap_chain.resize(device, context, size);
359    }
360
361    #[servo_tracing::instrument(skip_all, name = "SoftwareRenderingContext::present")]
362    fn present(&self) {
363        let device = &mut self.surfman_rendering_info.device.borrow_mut();
364        let context = &mut self.surfman_rendering_info.context.borrow_mut();
365        let _ = self
366            .swap_chain
367            .swap_buffers(device, context, PreserveBuffer::No);
368    }
369
370    fn make_current(&self) -> Result<(), Error> {
371        self.surfman_rendering_info.make_current()
372    }
373
374    fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
375        self.surfman_rendering_info.gleam_gl.clone()
376    }
377
378    fn glow_gl_api(&self) -> Arc<glow::Context> {
379        self.surfman_rendering_info.glow_gl.clone()
380    }
381
382    fn create_texture(
383        &self,
384        surface: Surface,
385    ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
386        self.surfman_rendering_info.create_texture(surface)
387    }
388
389    fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
390        self.surfman_rendering_info.destroy_texture(surface_texture)
391    }
392
393    fn connection(&self) -> Option<Connection> {
394        self.surfman_rendering_info.connection()
395    }
396}
397
398/// A [`RenderingContext`] that uses the `surfman` library to render to a
399/// `raw-window-handle` identified window. `surfman` will attempt to create an
400/// OpenGL context and surface for this window. This is a simple implementation
401/// of the [`RenderingContext`] trait, but by default it paints to the entire
402/// window surface.
403///
404/// If you would like to paint to only a portion of the window, consider using
405/// [`OffscreenRenderingContext`] by calling [`WindowRenderingContext::offscreen_context`].
406pub struct WindowRenderingContext {
407    /// The inner size of the window in physical pixels which excludes OS decorations.
408    size: Cell<PhysicalSize<u32>>,
409    surfman_context: SurfmanRenderingContext,
410}
411
412impl WindowRenderingContext {
413    pub fn new(
414        display_handle: DisplayHandle,
415        window_handle: WindowHandle,
416        size: PhysicalSize<u32>,
417    ) -> Result<Self, Error> {
418        Self::new_with_optional_refresh_driver(display_handle, window_handle, size, None)
419    }
420
421    pub fn new_with_refresh_driver(
422        display_handle: DisplayHandle,
423        window_handle: WindowHandle,
424        size: PhysicalSize<u32>,
425        refresh_driver: Rc<dyn RefreshDriver>,
426    ) -> Result<Self, Error> {
427        Self::new_with_optional_refresh_driver(
428            display_handle,
429            window_handle,
430            size,
431            Some(refresh_driver),
432        )
433    }
434
435    fn new_with_optional_refresh_driver(
436        display_handle: DisplayHandle,
437        window_handle: WindowHandle,
438        size: PhysicalSize<u32>,
439        refresh_driver: Option<Rc<dyn RefreshDriver>>,
440    ) -> Result<Self, Error> {
441        if size.width == 0 || size.height == 0 {
442            log::error!(
443                "Unable to create WindowRenderingContext with size under 1x1 ({size:?} provided)"
444            );
445            return Err(Error::Failed);
446        }
447
448        let connection = Connection::from_display_handle(display_handle)?;
449        let adapter = connection.create_adapter()?;
450        let surfman_context = SurfmanRenderingContext::new(&connection, &adapter, refresh_driver)?;
451
452        let native_widget = connection
453            .create_native_widget_from_window_handle(
454                window_handle,
455                Size2D::new(size.width as i32, size.height as i32),
456            )
457            .expect("Failed to create native widget");
458
459        let surface = surfman_context.create_surface(SurfaceType::Widget { native_widget })?;
460        surfman_context.bind_surface(surface)?;
461        surfman_context.make_current()?;
462
463        Ok(Self {
464            size: Cell::new(size),
465            surfman_context,
466        })
467    }
468
469    pub fn offscreen_context(
470        self: &Rc<Self>,
471        size: PhysicalSize<u32>,
472    ) -> OffscreenRenderingContext {
473        OffscreenRenderingContext::new(self.clone(), size)
474    }
475
476    /// Stop rendering to the window that was used to create this `WindowRenderingContext`
477    /// or last set with [`Self::set_window`].
478    ///
479    /// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s.
480    pub fn take_window(&self) -> Result<(), Error> {
481        let device = self.surfman_context.device.borrow_mut();
482        let mut context = self.surfman_context.context.borrow_mut();
483        let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap();
484        device.destroy_surface(&mut context, &mut surface)?;
485        Ok(())
486    }
487
488    /// Replace the window that this [`WindowRenderingContext`] renders to and give it a new
489    /// size.
490    ///
491    /// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s.
492    pub fn set_window(
493        &self,
494        window_handle: WindowHandle,
495        size: PhysicalSize<u32>,
496    ) -> Result<(), Error> {
497        let device = self.surfman_context.device.borrow_mut();
498        let mut context = self.surfman_context.context.borrow_mut();
499
500        let native_widget = device
501            .connection()
502            .create_native_widget_from_window_handle(
503                window_handle,
504                Size2D::new(size.width as i32, size.height as i32),
505            )
506            .expect("Failed to create native widget");
507
508        let surface_access = SurfaceAccess::GPUOnly;
509        let surface_type = SurfaceType::Widget { native_widget };
510        let surface = device.create_surface(&context, surface_access, surface_type)?;
511
512        device
513            .bind_surface_to_context(&mut context, surface)
514            .map_err(|(err, mut surface)| {
515                let _ = device.destroy_surface(&mut context, &mut surface);
516                err
517            })?;
518        device.make_context_current(&context)?;
519        Ok(())
520    }
521
522    pub fn surfman_details(&self) -> (RefMut<'_, Device>, RefMut<'_, Context>) {
523        (
524            self.surfman_context.device.borrow_mut(),
525            self.surfman_context.context.borrow_mut(),
526        )
527    }
528}
529
530impl RenderingContext for WindowRenderingContext {
531    fn prepare_for_rendering(&self) {
532        self.surfman_context.prepare_for_rendering();
533    }
534
535    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
536        self.surfman_context.read_to_image(source_rectangle)
537    }
538
539    fn size(&self) -> PhysicalSize<u32> {
540        self.size.get()
541    }
542
543    fn resize(&self, size: PhysicalSize<u32>) {
544        match self.surfman_context.resize_bound_surface(size) {
545            Ok(..) => self.size.set(size),
546            Err(error) => warn!("Error resizing surface: {error:?}"),
547        }
548    }
549
550    #[servo_tracing::instrument(skip_all, name = "WindowRenderingContext::present")]
551    fn present(&self) {
552        if let Err(error) = self.surfman_context.present_bound_surface() {
553            warn!("Error presenting surface: {error:?}");
554        }
555    }
556
557    fn make_current(&self) -> Result<(), Error> {
558        self.surfman_context.make_current()
559    }
560
561    fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
562        self.surfman_context.gleam_gl.clone()
563    }
564
565    fn glow_gl_api(&self) -> Arc<glow::Context> {
566        self.surfman_context.glow_gl.clone()
567    }
568
569    fn create_texture(
570        &self,
571        surface: Surface,
572    ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
573        self.surfman_context.create_texture(surface)
574    }
575
576    fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
577        self.surfman_context.destroy_texture(surface_texture)
578    }
579
580    fn connection(&self) -> Option<Connection> {
581        self.surfman_context.connection()
582    }
583
584    fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
585        self.surfman_context.refresh_driver()
586    }
587}
588
589struct Framebuffer {
590    gl: Rc<dyn Gl>,
591    framebuffer_id: gl::GLuint,
592    renderbuffer_id: gl::GLuint,
593    texture_id: gl::GLuint,
594}
595
596impl Framebuffer {
597    fn bind(&self) {
598        trace!("Binding FBO {}", self.framebuffer_id);
599        self.gl
600            .bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id)
601    }
602}
603
604impl Drop for Framebuffer {
605    fn drop(&mut self) {
606        self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
607        self.gl.delete_textures(&[self.texture_id]);
608        self.gl.delete_renderbuffers(&[self.renderbuffer_id]);
609        self.gl.delete_framebuffers(&[self.framebuffer_id]);
610    }
611}
612
613impl Framebuffer {
614    fn new(gl: Rc<dyn Gl>, size: PhysicalSize<u32>) -> Self {
615        let framebuffer_ids = gl.gen_framebuffers(1);
616        gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
617
618        let texture_ids = gl.gen_textures(1);
619        gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
620        gl.tex_image_2d(
621            gl::TEXTURE_2D,
622            0,
623            gl::RGBA as gl::GLint,
624            size.width as gl::GLsizei,
625            size.height as gl::GLsizei,
626            0,
627            gl::RGBA,
628            gl::UNSIGNED_BYTE,
629            None,
630        );
631        gl.tex_parameter_i(
632            gl::TEXTURE_2D,
633            gl::TEXTURE_MAG_FILTER,
634            gl::NEAREST as gl::GLint,
635        );
636        gl.tex_parameter_i(
637            gl::TEXTURE_2D,
638            gl::TEXTURE_MIN_FILTER,
639            gl::NEAREST as gl::GLint,
640        );
641
642        gl.framebuffer_texture_2d(
643            gl::FRAMEBUFFER,
644            gl::COLOR_ATTACHMENT0,
645            gl::TEXTURE_2D,
646            texture_ids[0],
647            0,
648        );
649
650        gl.bind_texture(gl::TEXTURE_2D, 0);
651
652        let renderbuffer_ids = gl.gen_renderbuffers(1);
653        let depth_rb = renderbuffer_ids[0];
654        gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
655        gl.renderbuffer_storage(
656            gl::RENDERBUFFER,
657            gl::DEPTH_COMPONENT24,
658            size.width as gl::GLsizei,
659            size.height as gl::GLsizei,
660        );
661        gl.framebuffer_renderbuffer(
662            gl::FRAMEBUFFER,
663            gl::DEPTH_ATTACHMENT,
664            gl::RENDERBUFFER,
665            depth_rb,
666        );
667
668        Self {
669            gl,
670            framebuffer_id: *framebuffer_ids
671                .first()
672                .expect("Guaranteed by GL operations"),
673            renderbuffer_id: *renderbuffer_ids
674                .first()
675                .expect("Guaranteed by GL operations"),
676            texture_id: *texture_ids.first().expect("Guaranteed by GL operations"),
677        }
678    }
679
680    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
681        Self::read_framebuffer_to_image(&self.gl, self.framebuffer_id, source_rectangle)
682    }
683
684    fn read_framebuffer_to_image(
685        gl: &Rc<dyn Gl>,
686        framebuffer_id: u32,
687        source_rectangle: DeviceIntRect,
688    ) -> Option<RgbaImage> {
689        gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
690
691        // For some reason, OSMesa fails to render on the 3rd
692        // attempt in headless mode, under some conditions.
693        // I think this can only be some kind of synchronization
694        // bug in OSMesa, but explicitly un-binding any vertex
695        // array here seems to work around that bug.
696        // See https://github.com/servo/servo/issues/18606.
697        gl.bind_vertex_array(0);
698
699        let mut pixels = gl.read_pixels(
700            source_rectangle.min.x,
701            source_rectangle.min.y,
702            source_rectangle.width(),
703            source_rectangle.height(),
704            gl::RGBA,
705            gl::UNSIGNED_BYTE,
706        );
707        let gl_error = gl.get_error();
708        if gl_error != gl::NO_ERROR {
709            warn!("GL error code 0x{gl_error:x} set after read_pixels");
710        }
711
712        // flip image vertically (texture is upside down)
713        let source_rectangle = source_rectangle.to_usize();
714        let orig_pixels = pixels.clone();
715        let stride = source_rectangle.width() * 4;
716        for y in 0..source_rectangle.height() {
717            let dst_start = y * stride;
718            let src_start = (source_rectangle.height() - y - 1) * stride;
719            let src_slice = &orig_pixels[src_start..src_start + stride];
720            pixels[dst_start..dst_start + stride].clone_from_slice(&src_slice[..stride]);
721        }
722
723        RgbaImage::from_raw(
724            source_rectangle.width() as u32,
725            source_rectangle.height() as u32,
726            pixels,
727        )
728    }
729}
730
731pub struct OffscreenRenderingContext {
732    parent_context: Rc<WindowRenderingContext>,
733    size: Cell<PhysicalSize<u32>>,
734    framebuffer: RefCell<Framebuffer>,
735}
736
737type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>;
738
739impl OffscreenRenderingContext {
740    fn new(parent_context: Rc<WindowRenderingContext>, size: PhysicalSize<u32>) -> Self {
741        assert!(
742            size.width != 0 && size.height != 0,
743            "Dimensions must be at least 1x1, got {size:?}",
744        );
745
746        let framebuffer = RefCell::new(Framebuffer::new(parent_context.gleam_gl_api(), size));
747        Self {
748            parent_context,
749            size: Cell::new(size),
750            framebuffer,
751        }
752    }
753
754    pub fn parent_context(&self) -> &WindowRenderingContext {
755        &self.parent_context
756    }
757
758    pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> {
759        // Don't accept a `None` context for the source framebuffer.
760        let front_framebuffer_id =
761            NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer)?;
762        let parent_context_framebuffer_id = self.parent_context.surfman_context.framebuffer();
763        let size = self.size.get();
764        let size = Size2D::new(size.width as i32, size.height as i32);
765        Some(Box::new(move |gl, target_rect| {
766            Self::blit_framebuffer(
767                gl,
768                Rect::new(Point2D::origin(), size.to_i32()),
769                front_framebuffer_id,
770                target_rect,
771                parent_context_framebuffer_id,
772            );
773        }))
774    }
775
776    #[expect(unsafe_code)]
777    fn blit_framebuffer(
778        gl: &glow::Context,
779        source_rect: Rect<i32>,
780        source_framebuffer_id: NativeFramebuffer,
781        target_rect: Rect<i32>,
782        target_framebuffer_id: Option<NativeFramebuffer>,
783    ) {
784        use glow::HasContext as _;
785        unsafe {
786            gl.clear_color(0.0, 0.0, 0.0, 0.0);
787            gl.scissor(
788                target_rect.origin.x,
789                target_rect.origin.y,
790                target_rect.width(),
791                target_rect.height(),
792            );
793            gl.enable(gl::SCISSOR_TEST);
794            gl.clear(gl::COLOR_BUFFER_BIT);
795            gl.disable(gl::SCISSOR_TEST);
796
797            gl.bind_framebuffer(gl::READ_FRAMEBUFFER, Some(source_framebuffer_id));
798            gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, target_framebuffer_id);
799
800            gl.blit_framebuffer(
801                source_rect.origin.x,
802                source_rect.origin.y,
803                source_rect.origin.x + source_rect.width(),
804                source_rect.origin.y + source_rect.height(),
805                target_rect.origin.x,
806                target_rect.origin.y,
807                target_rect.origin.x + target_rect.width(),
808                target_rect.origin.y + target_rect.height(),
809                gl::COLOR_BUFFER_BIT,
810                gl::NEAREST,
811            );
812            gl.bind_framebuffer(gl::FRAMEBUFFER, target_framebuffer_id);
813        }
814    }
815}
816
817impl RenderingContext for OffscreenRenderingContext {
818    fn size(&self) -> PhysicalSize<u32> {
819        self.size.get()
820    }
821
822    fn resize(&self, new_size: PhysicalSize<u32>) {
823        assert!(
824            new_size.width != 0 && new_size.height != 0,
825            "Dimensions must be at least 1x1, got {new_size:?}",
826        );
827
828        let old_size = self.size.get();
829        if old_size == new_size {
830            return;
831        }
832
833        let gl = self.parent_context.gleam_gl_api();
834        let new_framebuffer = Framebuffer::new(gl.clone(), new_size);
835
836        let old_framebuffer =
837            std::mem::replace(&mut *self.framebuffer.borrow_mut(), new_framebuffer);
838        self.size.set(new_size);
839
840        let blit_size = new_size.min(old_size);
841        let rect = Rect::new(
842            Point2D::origin(),
843            Size2D::new(blit_size.width, blit_size.height),
844        )
845        .to_i32();
846
847        let Some(old_framebuffer_id) =
848            NonZeroU32::new(old_framebuffer.framebuffer_id).map(NativeFramebuffer)
849        else {
850            return;
851        };
852        let new_framebuffer_id =
853            NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer);
854        Self::blit_framebuffer(
855            &self.glow_gl_api(),
856            rect,
857            old_framebuffer_id,
858            rect,
859            new_framebuffer_id,
860        );
861    }
862
863    fn prepare_for_rendering(&self) {
864        self.framebuffer.borrow().bind();
865    }
866
867    fn present(&self) {}
868
869    fn make_current(&self) -> Result<(), surfman::Error> {
870        self.parent_context.make_current()
871    }
872
873    fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
874        self.parent_context.gleam_gl_api()
875    }
876
877    fn glow_gl_api(&self) -> Arc<glow::Context> {
878        self.parent_context.glow_gl_api()
879    }
880
881    fn create_texture(
882        &self,
883        surface: Surface,
884    ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
885        self.parent_context.create_texture(surface)
886    }
887
888    fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
889        self.parent_context.destroy_texture(surface_texture)
890    }
891
892    fn connection(&self) -> Option<Connection> {
893        self.parent_context.connection()
894    }
895
896    fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
897        self.framebuffer.borrow().read_to_image(source_rectangle)
898    }
899
900    fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> {
901        self.parent_context().refresh_driver()
902    }
903}
904
905fn print_diagnostics_information_on_context_creation_failure(
906    device: &Device,
907    desired_api: GLApi,
908    desired_version: GLVersion,
909) {
910    println!("===============================================================");
911    println!(
912        "Could not create a {desired_api:?} {:?}.{:?} context when starting Servo.",
913        desired_version.major, desired_version.minor
914    );
915
916    let version = surfman::GLVersion { major: 1, minor: 0 };
917    match device
918        .create_context_descriptor(&ContextAttributes {
919            flags: ContextAttributeFlags::empty(),
920            version,
921        })
922        .and_then(|context_descriptor| device.create_context(&context_descriptor, None))
923    {
924        Ok(mut context) => {
925            #[expect(unsafe_code)]
926            let glow_gl = unsafe {
927                glow::Context::from_loader_function(|function_name| {
928                    device.get_proc_address(&context, function_name)
929                })
930            };
931
932            println!(
933                "It's likely that your version of OpenGL ({:?}.{:?}) is too old.",
934                glow_gl.version().major,
935                glow_gl.version().minor
936            );
937            println!("If not, please file a bug at https://github.com/servo/servo/issues.");
938            let _ = device.destroy_context(&mut context);
939        },
940        Err(_) => {
941            println!("Could not create any {desired_api:?} context.");
942            println!("Ensure that OpenGL is working on your system.");
943            println!("If it is, please file a bug at https://github.com/servo/servo/issues.");
944        },
945    }
946    println!("===============================================================\n");
947}
948
949#[cfg(test)]
950mod test {
951    use dpi::PhysicalSize;
952    use euclid::{Box2D, Point2D, Size2D};
953    use gleam::gl;
954    use image::Rgba;
955    use surfman::{Connection, ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion};
956
957    use super::Framebuffer;
958    use crate::rendering_context::SoftwareRenderingContext;
959
960    #[test]
961    #[expect(unsafe_code)]
962    fn test_read_pixels() -> Result<(), Error> {
963        let connection = Connection::new()?;
964        let adapter = connection.create_software_adapter()?;
965        let device = connection.create_device(&adapter)?;
966        let context_descriptor = device.create_context_descriptor(&ContextAttributes {
967            version: GLVersion::new(3, 0),
968            flags: ContextAttributeFlags::empty(),
969        })?;
970        let mut context = device.create_context(&context_descriptor, None)?;
971
972        let gl = match connection.gl_api() {
973            GLApi::GL => unsafe { gl::GlFns::load_with(|s| device.get_proc_address(&context, s)) },
974            GLApi::GLES => unsafe {
975                gl::GlesFns::load_with(|s| device.get_proc_address(&context, s))
976            },
977        };
978
979        device.make_context_current(&context)?;
980
981        {
982            const SIZE: u32 = 16;
983            let framebuffer = Framebuffer::new(gl, PhysicalSize::new(SIZE, SIZE));
984            framebuffer.bind();
985            framebuffer
986                .gl
987                .clear_color(12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0, 78.0 / 255.0);
988            framebuffer.gl.clear(gl::COLOR_BUFFER_BIT);
989
990            let rect = Box2D::from_origin_and_size(Point2D::zero(), Size2D::new(SIZE, SIZE));
991            let img = framebuffer
992                .read_to_image(rect.to_i32())
993                .expect("Should have been able to read back image.");
994            assert_eq!(img.width(), SIZE);
995            assert_eq!(img.height(), SIZE);
996
997            let expected_pixel: Rgba<u8> = Rgba([12, 34, 56, 78]);
998            assert!(img.pixels().all(|&p| p == expected_pixel));
999        }
1000
1001        device.destroy_context(&mut context)?;
1002
1003        Ok(())
1004    }
1005
1006    #[test]
1007    fn test_minimum_size_error() {
1008        let result = SoftwareRenderingContext::new(PhysicalSize {
1009            width: 0,
1010            height: 1,
1011        });
1012        match result {
1013            Err(surfman::Error::Failed) => (),
1014            _ => panic!("Expected {:?}", surfman::Error::Failed),
1015        }
1016    }
1017}