compositing_traits/
rendering_context.rs

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