Skip to main content

paint_api/
rendering_context.rs

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