/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/shared/paint/rendering_context.rs
1 018 строк
35 KB
Martin Robinson
paint/servoshell: Optimize resizing so that it is free of tearing and drift (#45456)
09 июн 2026, 23:24
Не верифицирован
09 июн 2026, 23:24
6dee86e
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![deny(unsafe_code)] use std::cell::{Cell, RefCell, RefMut}; use std::num::NonZeroU32; use std::rc::Rc; use std::sync::Arc; use dpi::PhysicalSize; use embedder_traits::RefreshDriver; use euclid::default::{Rect, Size2D as UntypedSize2D}; use euclid::{Point2D, Size2D}; use gleam::gl::{self, Gl}; use glow::{HasContext, NativeFramebuffer}; use image::RgbaImage; use log::{debug, trace, warn}; use raw_window_handle::{DisplayHandle, WindowHandle}; pub use surfman::Error; use surfman::chains::{PreserveBuffer, SwapChain}; use surfman::{ Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi, GLVersion, NativeContext, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture, SurfaceType, }; use webrender_api::units::{DeviceIntRect, DevicePixel}; /// The `RenderingContext` trait defines a set of methods for managing /// an OpenGL or GLES rendering context. /// Implementors of this trait are responsible for handling the creation, /// management, and destruction of the rendering context and its associated /// resources. pub trait RenderingContext { /// Prepare this [`RenderingContext`] to be rendered upon by Servo. For instance, /// by binding a framebuffer to the current OpenGL context. fn prepare_for_rendering(&self) {} /// Read the contents of this [`Renderingcontext`] into an in-memory image. If the /// image cannot be read (for instance, if no rendering has taken place yet), then /// `None` is returned. /// /// In a double-buffered [`RenderingContext`] this is expected to read from the back /// buffer. That means that once Servo renders to the context, this should return those /// results, even before [`RenderingContext::present`] is called. fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage>; /// Get the current size of this [`RenderingContext`]. fn size(&self) -> PhysicalSize<u32>; /// Get the current size of this [`RenderingContext`] as [`Size2D`]. fn size2d(&self) -> Size2D<u32, DevicePixel> { let size = self.size(); Size2D::new(size.width, size.height) } /// Resizes the rendering surface to the given size. fn resize(&self, size: PhysicalSize<u32>); /// Presents the rendered frame to the screen. In a double-buffered context, this would /// swap buffers. fn present(&self); /// Makes the context the current OpenGL context for this thread. /// After calling this function, it is valid to use OpenGL rendering /// commands. fn make_current(&self) -> Result<(), Error>; /// Returns the `gleam` version of the OpenGL or GLES API. fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl>; /// Returns the OpenGL or GLES API. fn glow_gl_api(&self) -> Arc<glow::Context>; /// Creates a texture from a given surface and returns the surface texture, /// the OpenGL texture object, and the size of the surface. Default to `None`. fn create_texture( &self, _surface: Surface, ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> { None } /// Destroys the texture and returns the surface. Default to `None`. fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> { None } /// The connection to the display server for WebGL. Default to `None`. fn connection(&self) -> Option<Connection> { None } /// Return the [`RefreshDriver`] for this [`RenderingContext`]. If `None` is returned, /// then the default timer-based [`RefreshDriver`] will be used. fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> { None } } /// A rendering context that uses the Surfman library to create and manage /// the OpenGL context and surface. This struct provides the default implementation /// of the `RenderingContext` trait, handling the creation, management, and destruction /// of the rendering context and its associated resources. /// /// The `SurfmanRenderingContext` struct encapsulates the necessary data and methods /// to interact with the Surfman library, including creating surfaces, binding surfaces, /// resizing surfaces, presenting rendered frames, and managing the OpenGL context state. struct SurfmanRenderingContext { gleam_gl: Rc<dyn Gl>, glow_gl: Arc<glow::Context>, device: RefCell<Device>, context: RefCell<Context>, refresh_driver: Option<Rc<dyn RefreshDriver>>, } impl Drop for SurfmanRenderingContext { fn drop(&mut self) { let device = &mut self.device.borrow_mut(); let context = &mut self.context.borrow_mut(); let _ = device.destroy_context(context); } } impl SurfmanRenderingContext { fn new( connection: &Connection, adapter: &Adapter, refresh_driver: Option<Rc<dyn RefreshDriver>>, ) -> Result<Self, Error> { let device = connection.create_device(adapter)?; let flags = ContextAttributeFlags::ALPHA | ContextAttributeFlags::DEPTH | ContextAttributeFlags::STENCIL; let gl_api = connection.gl_api(); let version = match &gl_api { GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 }, GLApi::GL => surfman::GLVersion { major: 3, minor: 2 }, }; let context_descriptor = device.create_context_descriptor(&ContextAttributes { flags, version })?; let context = device .create_context(&context_descriptor, None) .inspect_err(|_| { print_diagnostics_information_on_context_creation_failure(&device, gl_api, version) })?; #[expect(unsafe_code)] let gleam_gl = { match gl_api { GLApi::GL => unsafe { gl::GlFns::load_with(|func_name| device.get_proc_address(&context, func_name)) }, GLApi::GLES => unsafe { gl::GlesFns::load_with(|func_name| device.get_proc_address(&context, func_name)) }, } }; #[expect(unsafe_code)] let glow_gl = unsafe { glow::Context::from_loader_function(|function_name| { device.get_proc_address(&context, function_name) }) }; Ok(SurfmanRenderingContext { gleam_gl, glow_gl: Arc::new(glow_gl), device: RefCell::new(device), context: RefCell::new(context), refresh_driver, }) } fn create_surface(&self, surface_type: SurfaceType<NativeWidget>) -> Result<Surface, Error> { let device = &mut self.device.borrow_mut(); let context = &self.context.borrow(); device.create_surface(context, SurfaceAccess::GPUOnly, surface_type) } fn bind_surface(&self, surface: Surface) -> Result<(), Error> { let device = &self.device.borrow(); let context = &mut self.context.borrow_mut(); device .bind_surface_to_context(context, surface) .map_err(|(err, mut surface)| { let _ = device.destroy_surface(context, &mut surface); err })?; Ok(()) } fn create_attached_swap_chain(&self) -> Result<SwapChain<Device>, Error> { let device = &mut self.device.borrow_mut(); let context = &mut self.context.borrow_mut(); SwapChain::create_attached(device, context, SurfaceAccess::GPUOnly) } fn resize_bound_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> { if size.width == 0 || size.height == 0 { log::error!("Unable to resize to size under 1x1 ({size:?} provided)"); return Err(Error::Failed); } let size = Size2D::new(size.width as i32, size.height as i32); self.device .borrow() .resize_bound_surface(&mut self.context.borrow_mut(), size) } fn present_bound_surface(&self) -> Result<(), Error> { self.device .borrow() .present_bound_surface(&mut self.context.borrow_mut()) } #[expect(dead_code)] fn native_context(&self) -> NativeContext { let device = &self.device.borrow(); let context = &self.context.borrow(); device.native_context(context) } fn framebuffer(&self) -> Option<NativeFramebuffer> { let device = &self.device.borrow(); let context = &self.context.borrow(); device .context_surface_info(context) .unwrap_or(None) .and_then(|info| info.framebuffer_object) } fn prepare_for_rendering(&self) { let framebuffer_id = self .framebuffer() .map_or(0, |framebuffer| framebuffer.0.into()); self.gleam_gl .bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_id); } fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> { let framebuffer_id = self .framebuffer() .map_or(0, |framebuffer| framebuffer.0.into()); Framebuffer::read_framebuffer_to_image(&self.gleam_gl, framebuffer_id, source_rectangle) } fn make_current(&self) -> Result<(), Error> { let device = &self.device.borrow(); let context = &mut self.context.borrow(); device.make_context_current(context) } fn create_texture( &self, surface: Surface, ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> { let device = &self.device.borrow(); let context = &mut self.context.borrow_mut(); let SurfaceInfo { id: front_buffer_id, size, .. } = device.surface_info(&surface); debug!("... getting texture for surface {:?}", front_buffer_id); let surface_texture = device.create_surface_texture(context, surface).unwrap(); let gl_texture = device .surface_texture_object(&surface_texture) .map(|tex| tex.0.get()) .unwrap_or(0); Some((surface_texture, gl_texture, size)) } fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> { let device = &self.device.borrow(); let context = &mut self.context.borrow_mut(); device .destroy_surface_texture(context, surface_texture) .map_err(|(error, _)| error) .ok() } fn connection(&self) -> Option<Connection> { Some(self.device.borrow().connection()) } fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> { self.refresh_driver.clone() } } /// A software rendering context that uses a software OpenGL implementation to render /// Servo. This will generally have bad performance, but can be used in situations where /// it is more convenient to have consistent, but slower display output. /// /// The results of the render can be accessed via [`RenderingContext::read_to_image`]. pub struct SoftwareRenderingContext { size: Cell<PhysicalSize<u32>>, surfman_rendering_info: SurfmanRenderingContext, swap_chain: SwapChain<Device>, } impl SoftwareRenderingContext { pub fn new(size: PhysicalSize<u32>) -> Result<Self, Error> { if size.width == 0 || size.height == 0 { log::error!( "Unable to create SoftwareRenderingContext with size under 1x1 ({size:?} provided)" ); return Err(Error::Failed); } let connection = Connection::new()?; let adapter = connection.create_software_adapter()?; let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter, None)?; let surfman_size = Size2D::new(size.width as i32, size.height as i32); let surface = surfman_rendering_info.create_surface(SurfaceType::Generic { size: surfman_size })?; surfman_rendering_info.bind_surface(surface)?; surfman_rendering_info.make_current()?; let swap_chain = surfman_rendering_info.create_attached_swap_chain()?; Ok(SoftwareRenderingContext { size: Cell::new(size), surfman_rendering_info, swap_chain, }) } } impl Drop for SoftwareRenderingContext { fn drop(&mut self) { let device = &mut self.surfman_rendering_info.device.borrow_mut(); let context = &mut self.surfman_rendering_info.context.borrow_mut(); let _ = self.swap_chain.destroy(device, context); } } impl RenderingContext for SoftwareRenderingContext { fn prepare_for_rendering(&self) { self.surfman_rendering_info.prepare_for_rendering(); } fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> { self.surfman_rendering_info.read_to_image(source_rectangle) } fn size(&self) -> PhysicalSize<u32> { self.size.get() } fn resize(&self, size: PhysicalSize<u32>) { assert!( size.width > 0 && size.height > 0, "Dimensions must be at least 1x1, got {size:?}", ); if self.size.get() == size { return; } self.size.set(size); let device = &mut self.surfman_rendering_info.device.borrow_mut(); let context = &mut self.surfman_rendering_info.context.borrow_mut(); let size = Size2D::new(size.width as i32, size.height as i32); let _ = self.swap_chain.resize(device, context, size); } #[servo_tracing::instrument(skip_all, name = "SoftwareRenderingContext::present")] fn present(&self) { let device = &mut self.surfman_rendering_info.device.borrow_mut(); let context = &mut self.surfman_rendering_info.context.borrow_mut(); let _ = self .swap_chain .swap_buffers(device, context, PreserveBuffer::No); } fn make_current(&self) -> Result<(), Error> { self.surfman_rendering_info.make_current() } fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> { self.surfman_rendering_info.gleam_gl.clone() } fn glow_gl_api(&self) -> Arc<glow::Context> { self.surfman_rendering_info.glow_gl.clone() } fn create_texture( &self, surface: Surface, ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> { self.surfman_rendering_info.create_texture(surface) } fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> { self.surfman_rendering_info.destroy_texture(surface_texture) } fn connection(&self) -> Option<Connection> { self.surfman_rendering_info.connection() } } /// A [`RenderingContext`] that uses the `surfman` library to render to a /// `raw-window-handle` identified window. `surfman` will attempt to create an /// OpenGL context and surface for this window. This is a simple implementation /// of the [`RenderingContext`] trait, but by default it paints to the entire /// window surface. /// /// If you would like to paint to only a portion of the window, consider using /// [`OffscreenRenderingContext`] by calling [`WindowRenderingContext::offscreen_context`]. pub struct WindowRenderingContext { /// The inner size of the window in physical pixels which excludes OS decorations. size: Cell<PhysicalSize<u32>>, surfman_context: SurfmanRenderingContext, } impl WindowRenderingContext { pub fn new( display_handle: DisplayHandle, window_handle: WindowHandle, size: PhysicalSize<u32>, ) -> Result<Self, Error> { Self::new_with_optional_refresh_driver(display_handle, window_handle, size, None) } pub fn new_with_refresh_driver( display_handle: DisplayHandle, window_handle: WindowHandle, size: PhysicalSize<u32>, refresh_driver: Rc<dyn RefreshDriver>, ) -> Result<Self, Error> { Self::new_with_optional_refresh_driver( display_handle, window_handle, size, Some(refresh_driver), ) } fn new_with_optional_refresh_driver( display_handle: DisplayHandle, window_handle: WindowHandle, size: PhysicalSize<u32>, refresh_driver: Option<Rc<dyn RefreshDriver>>, ) -> Result<Self, Error> { if size.width == 0 || size.height == 0 { log::error!( "Unable to create WindowRenderingContext with size under 1x1 ({size:?} provided)" ); return Err(Error::Failed); } let connection = Connection::from_display_handle(display_handle)?; let adapter = connection.create_adapter()?; let surfman_context = SurfmanRenderingContext::new(&connection, &adapter, refresh_driver)?; let native_widget = connection .create_native_widget_from_window_handle( window_handle, Size2D::new(size.width as i32, size.height as i32), ) .expect("Failed to create native widget"); let surface = surfman_context.create_surface(SurfaceType::Widget { native_widget })?; surfman_context.bind_surface(surface)?; surfman_context.make_current()?; Ok(Self { size: Cell::new(size), surfman_context, }) } pub fn offscreen_context( self: &Rc<Self>, size: PhysicalSize<u32>, ) -> OffscreenRenderingContext { OffscreenRenderingContext::new(self.clone(), size) } /// Stop rendering to the window that was used to create this `WindowRenderingContext` /// or last set with [`Self::set_window`]. /// /// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s. pub fn take_window(&self) -> Result<(), Error> { let device = self.surfman_context.device.borrow_mut(); let mut context = self.surfman_context.context.borrow_mut(); let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap(); device.destroy_surface(&mut context, &mut surface)?; Ok(()) } /// Replace the window that this [`WindowRenderingContext`] renders to and give it a new /// size. /// /// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s. pub fn set_window( &self, window_handle: WindowHandle, size: PhysicalSize<u32>, ) -> Result<(), Error> { let device = self.surfman_context.device.borrow_mut(); let mut context = self.surfman_context.context.borrow_mut(); let native_widget = device .connection() .create_native_widget_from_window_handle( window_handle, Size2D::new(size.width as i32, size.height as i32), ) .expect("Failed to create native widget"); let surface_access = SurfaceAccess::GPUOnly; let surface_type = SurfaceType::Widget { native_widget }; let surface = device.create_surface(&context, surface_access, surface_type)?; device .bind_surface_to_context(&mut context, surface) .map_err(|(err, mut surface)| { let _ = device.destroy_surface(&mut context, &mut surface); err })?; device.make_context_current(&context)?; Ok(()) } pub fn surfman_details(&self) -> (RefMut<'_, Device>, RefMut<'_, Context>) { ( self.surfman_context.device.borrow_mut(), self.surfman_context.context.borrow_mut(), ) } } impl RenderingContext for WindowRenderingContext { fn prepare_for_rendering(&self) { self.surfman_context.prepare_for_rendering(); } fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> { self.surfman_context.read_to_image(source_rectangle) } fn size(&self) -> PhysicalSize<u32> { self.size.get() } fn resize(&self, size: PhysicalSize<u32>) { match self.surfman_context.resize_bound_surface(size) { Ok(..) => self.size.set(size), Err(error) => warn!("Error resizing surface: {error:?}"), } } #[servo_tracing::instrument(skip_all, name = "WindowRenderingContext::present")] fn present(&self) { if let Err(error) = self.surfman_context.present_bound_surface() { warn!("Error presenting surface: {error:?}"); } } fn make_current(&self) -> Result<(), Error> { self.surfman_context.make_current() } fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> { self.surfman_context.gleam_gl.clone() } fn glow_gl_api(&self) -> Arc<glow::Context> { self.surfman_context.glow_gl.clone() } fn create_texture( &self, surface: Surface, ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> { self.surfman_context.create_texture(surface) } fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> { self.surfman_context.destroy_texture(surface_texture) } fn connection(&self) -> Option<Connection> { self.surfman_context.connection() } fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> { self.surfman_context.refresh_driver() } } struct Framebuffer { gl: Rc<dyn Gl>, framebuffer_id: gl::GLuint, renderbuffer_id: gl::GLuint, texture_id: gl::GLuint, } impl Framebuffer { fn bind(&self) { trace!("Binding FBO {}", self.framebuffer_id); self.gl .bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id) } } impl Drop for Framebuffer { fn drop(&mut self) { self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0); self.gl.delete_textures(&[self.texture_id]); self.gl.delete_renderbuffers(&[self.renderbuffer_id]); self.gl.delete_framebuffers(&[self.framebuffer_id]); } } impl Framebuffer { fn new(gl: Rc<dyn Gl>, size: PhysicalSize<u32>) -> Self { let framebuffer_ids = gl.gen_framebuffers(1); gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]); let texture_ids = gl.gen_textures(1); gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]); gl.tex_image_2d( gl::TEXTURE_2D, 0, gl::RGBA as gl::GLint, size.width as gl::GLsizei, size.height as gl::GLsizei, 0, gl::RGBA, gl::UNSIGNED_BYTE, None, ); gl.tex_parameter_i( gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as gl::GLint, ); gl.tex_parameter_i( gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as gl::GLint, ); gl.framebuffer_texture_2d( gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, texture_ids[0], 0, ); gl.bind_texture(gl::TEXTURE_2D, 0); let renderbuffer_ids = gl.gen_renderbuffers(1); let depth_rb = renderbuffer_ids[0]; gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb); gl.renderbuffer_storage( gl::RENDERBUFFER, gl::DEPTH_COMPONENT24, size.width as gl::GLsizei, size.height as gl::GLsizei, ); gl.framebuffer_renderbuffer( gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, gl::RENDERBUFFER, depth_rb, ); Self { gl, framebuffer_id: *framebuffer_ids .first() .expect("Guaranteed by GL operations"), renderbuffer_id: *renderbuffer_ids .first() .expect("Guaranteed by GL operations"), texture_id: *texture_ids.first().expect("Guaranteed by GL operations"), } } fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> { Self::read_framebuffer_to_image(&self.gl, self.framebuffer_id, source_rectangle) } fn read_framebuffer_to_image( gl: &Rc<dyn Gl>, framebuffer_id: u32, source_rectangle: DeviceIntRect, ) -> Option<RgbaImage> { gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id); // For some reason, OSMesa fails to render on the 3rd // attempt in headless mode, under some conditions. // I think this can only be some kind of synchronization // bug in OSMesa, but explicitly un-binding any vertex // array here seems to work around that bug. // See https://github.com/servo/servo/issues/18606. gl.bind_vertex_array(0); let mut pixels = gl.read_pixels( source_rectangle.min.x, source_rectangle.min.y, source_rectangle.width(), source_rectangle.height(), gl::RGBA, gl::UNSIGNED_BYTE, ); let gl_error = gl.get_error(); if gl_error != gl::NO_ERROR { warn!("GL error code 0x{gl_error:x} set after read_pixels"); } // flip image vertically (texture is upside down) let source_rectangle = source_rectangle.to_usize(); let orig_pixels = pixels.clone(); let stride = source_rectangle.width() * 4; for y in 0..source_rectangle.height() { let dst_start = y * stride; let src_start = (source_rectangle.height() - y - 1) * stride; let src_slice = &orig_pixels[src_start..src_start + stride]; pixels[dst_start..dst_start + stride].clone_from_slice(&src_slice[..stride]); } RgbaImage::from_raw( source_rectangle.width() as u32, source_rectangle.height() as u32, pixels, ) } } pub struct OffscreenRenderingContext { parent_context: Rc<WindowRenderingContext>, size: Cell<PhysicalSize<u32>>, framebuffer: RefCell<Framebuffer>, } type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>; impl OffscreenRenderingContext { fn new(parent_context: Rc<WindowRenderingContext>, size: PhysicalSize<u32>) -> Self { assert!( size.width != 0 && size.height != 0, "Dimensions must be at least 1x1, got {size:?}", ); let framebuffer = RefCell::new(Framebuffer::new(parent_context.gleam_gl_api(), size)); Self { parent_context, size: Cell::new(size), framebuffer, } } pub fn parent_context(&self) -> &WindowRenderingContext { &self.parent_context } pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> { // Don't accept a `None` context for the source framebuffer. let front_framebuffer_id = NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer)?; let parent_context_framebuffer_id = self.parent_context.surfman_context.framebuffer(); let size = self.size.get(); let size = Size2D::new(size.width as i32, size.height as i32); Some(Box::new(move |gl, target_rect| { Self::blit_framebuffer( gl, Rect::new(Point2D::origin(), size.to_i32()), front_framebuffer_id, target_rect, parent_context_framebuffer_id, ); })) } #[expect(unsafe_code)] fn blit_framebuffer( gl: &glow::Context, source_rect: Rect<i32>, source_framebuffer_id: NativeFramebuffer, target_rect: Rect<i32>, target_framebuffer_id: Option<NativeFramebuffer>, ) { use glow::HasContext as _; unsafe { gl.clear_color(0.0, 0.0, 0.0, 0.0); gl.scissor( target_rect.origin.x, target_rect.origin.y, target_rect.width(), target_rect.height(), ); gl.enable(gl::SCISSOR_TEST); gl.clear(gl::COLOR_BUFFER_BIT); gl.disable(gl::SCISSOR_TEST); gl.bind_framebuffer(gl::READ_FRAMEBUFFER, Some(source_framebuffer_id)); gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, target_framebuffer_id); gl.blit_framebuffer( source_rect.origin.x, source_rect.origin.y, source_rect.origin.x + source_rect.width(), source_rect.origin.y + source_rect.height(), target_rect.origin.x, target_rect.origin.y, target_rect.origin.x + target_rect.width(), target_rect.origin.y + target_rect.height(), gl::COLOR_BUFFER_BIT, gl::NEAREST, ); gl.bind_framebuffer(gl::FRAMEBUFFER, target_framebuffer_id); } } } impl RenderingContext for OffscreenRenderingContext { fn size(&self) -> PhysicalSize<u32> { self.size.get() } fn resize(&self, new_size: PhysicalSize<u32>) { assert!( new_size.width != 0 && new_size.height != 0, "Dimensions must be at least 1x1, got {new_size:?}", ); let old_size = self.size.get(); if old_size == new_size { return; } let gl = self.parent_context.gleam_gl_api(); let new_framebuffer = Framebuffer::new(gl.clone(), new_size); let old_framebuffer = std::mem::replace(&mut *self.framebuffer.borrow_mut(), new_framebuffer); self.size.set(new_size); let blit_size = new_size.min(old_size); let rect = Rect::new( Point2D::origin(), Size2D::new(blit_size.width, blit_size.height), ) .to_i32(); let Some(old_framebuffer_id) = NonZeroU32::new(old_framebuffer.framebuffer_id).map(NativeFramebuffer) else { return; }; let new_framebuffer_id = NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer); Self::blit_framebuffer( &self.glow_gl_api(), rect, old_framebuffer_id, rect, new_framebuffer_id, ); } fn prepare_for_rendering(&self) { self.framebuffer.borrow().bind(); } fn present(&self) {} fn make_current(&self) -> Result<(), surfman::Error> { self.parent_context.make_current() } fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> { self.parent_context.gleam_gl_api() } fn glow_gl_api(&self) -> Arc<glow::Context> { self.parent_context.glow_gl_api() } fn create_texture( &self, surface: Surface, ) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> { self.parent_context.create_texture(surface) } fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> { self.parent_context.destroy_texture(surface_texture) } fn connection(&self) -> Option<Connection> { self.parent_context.connection() } fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> { self.framebuffer.borrow().read_to_image(source_rectangle) } fn refresh_driver(&self) -> Option<Rc<dyn RefreshDriver>> { self.parent_context().refresh_driver() } } fn print_diagnostics_information_on_context_creation_failure( device: &Device, desired_api: GLApi, desired_version: GLVersion, ) { println!("==============================================================="); println!( "Could not create a {desired_api:?} {:?}.{:?} context when starting Servo.", desired_version.major, desired_version.minor ); let version = surfman::GLVersion { major: 1, minor: 0 }; match device .create_context_descriptor(&ContextAttributes { flags: ContextAttributeFlags::empty(), version, }) .and_then(|context_descriptor| device.create_context(&context_descriptor, None)) { Ok(mut context) => { #[expect(unsafe_code)] let glow_gl = unsafe { glow::Context::from_loader_function(|function_name| { device.get_proc_address(&context, function_name) }) }; println!( "It's likely that your version of OpenGL ({:?}.{:?}) is too old.", glow_gl.version().major, glow_gl.version().minor ); println!("If not, please file a bug at https://github.com/servo/servo/issues."); let _ = device.destroy_context(&mut context); }, Err(_) => { println!("Could not create any {desired_api:?} context."); println!("Ensure that OpenGL is working on your system."); println!("If it is, please file a bug at https://github.com/servo/servo/issues."); }, } println!("===============================================================\n"); } #[cfg(test)] mod test { use dpi::PhysicalSize; use euclid::{Box2D, Point2D, Size2D}; use gleam::gl; use image::Rgba; use surfman::{Connection, ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion}; use super::Framebuffer; use crate::rendering_context::SoftwareRenderingContext; #[test] #[expect(unsafe_code)] fn test_read_pixels() -> Result<(), Error> { let connection = Connection::new()?; let adapter = connection.create_software_adapter()?; let device = connection.create_device(&adapter)?; let context_descriptor = device.create_context_descriptor(&ContextAttributes { version: GLVersion::new(3, 0), flags: ContextAttributeFlags::empty(), })?; let mut context = device.create_context(&context_descriptor, None)?; let gl = match connection.gl_api() { GLApi::GL => unsafe { gl::GlFns::load_with(|s| device.get_proc_address(&context, s)) }, GLApi::GLES => unsafe { gl::GlesFns::load_with(|s| device.get_proc_address(&context, s)) }, }; device.make_context_current(&context)?; { const SIZE: u32 = 16; let framebuffer = Framebuffer::new(gl, PhysicalSize::new(SIZE, SIZE)); framebuffer.bind(); framebuffer .gl .clear_color(12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0, 78.0 / 255.0); framebuffer.gl.clear(gl::COLOR_BUFFER_BIT); let rect = Box2D::from_origin_and_size(Point2D::zero(), Size2D::new(SIZE, SIZE)); let img = framebuffer .read_to_image(rect.to_i32()) .expect("Should have been able to read back image."); assert_eq!(img.width(), SIZE); assert_eq!(img.height(), SIZE); let expected_pixel: Rgba<u8> = Rgba([12, 34, 56, 78]); assert!(img.pixels().all(|&p| p == expected_pixel)); } device.destroy_context(&mut context)?; Ok(()) } #[test] fn test_minimum_size_error() { let result = SoftwareRenderingContext::new(PhysicalSize { width: 0, height: 1, }); match result { Err(surfman::Error::Failed) => (), _ => panic!("Expected {:?}", surfman::Error::Failed), } } }