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