servo/components/shared/webrender/rendering_context.rs
Martin Robinson 06d4272462
libservo: Stop double-buffering OffscreenRenderingContext (#35638)
The `OffscreenRenderingContext` does not need to be double-buffered.
Instead, when resizing the framebuffer, create a new one and blit the
old contents onto the new surface. This allows immediately displaying
the contents without having to render paint the WebRender scene one more
time. In addition to speeding up the rendering pipeline, the goal here
is to reduce flickering during resizes (though there is more work to
do).

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2025-02-28 11:41:56 +00:00

887 lines
30 KiB
Rust

/* 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 euclid::default::{Rect, Size2D as UntypedSize2D};
use euclid::{Point2D, Size2D};
use gleam::gl::{self, Gl};
use glow::NativeFramebuffer;
use image::RgbaImage;
use log::{debug, trace, warn};
use raw_window_handle::{DisplayHandle, WindowHandle};
use surfman::chains::{PreserveBuffer, SwapChain};
pub use surfman::Error;
use surfman::{
Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
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
}
}
/// 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>,
}
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) -> Result<Self, Error> {
let mut 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)?;
#[allow(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))
},
}
};
#[allow(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),
})
}
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_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> {
let size = Size2D::new(size.width as i32, size.height as i32);
let device = &mut self.device.borrow_mut();
let context = &mut self.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
device.resize_surface(context, &mut surface, size)?;
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})
}
fn present_bound_surface(&self) -> Result<(), Error> {
let device = &self.device.borrow();
let context = &mut self.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
device.present_surface(context, &mut surface)?;
device
.bind_surface_to_context(context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(context, &mut surface);
err
})
}
#[allow(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())
}
}
/// 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> {
let connection = Connection::new()?;
let adapter = connection.create_software_adapter()?;
let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter)?;
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>) {
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);
}
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`] crate, 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 {
size: Cell<PhysicalSize<u32>>,
surfman_context: SurfmanRenderingContext,
}
impl WindowRenderingContext {
pub fn new(
display_handle: DisplayHandle,
window_handle: WindowHandle,
size: PhysicalSize<u32>,
) -> Result<Self, Error> {
let connection = Connection::from_display_handle(display_handle)?;
let adapter = connection.create_adapter()?;
let surfman_context = SurfmanRenderingContext::new(&connection, &adapter)?;
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 mut 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_surface(size) {
Ok(..) => self.size.set(size),
Err(error) => warn!("Error resizing surface: {error:?}"),
}
}
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()
}
}
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 {
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,
);
}))
}
#[allow(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>) {
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)
}
}
#[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;
#[test]
#[allow(unsafe_code)]
fn test_read_pixels() -> Result<(), Error> {
let connection = Connection::new()?;
let adapter = connection.create_software_adapter()?;
let mut 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(())
}
}