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