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