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