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