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