Add initial support for offscreen rendering (#30767)

* Offscreen rendering

* shared memory case never actually rendered to backbuffer

* fix compile errors (in theory) when gl crate feature disabled

* update doc comments

* remove dark CentralPanel border covering edges of viewport

* clear to transparent, to avoid pink artifacts

* fix mouse input for browser being consumed by egui

* avoid destroying OpenGL resources unless resizing window

* clean up compositing::gl

* fix flickering around edges after resizing window

* unset invalidate_last_render_target after invalidating

* fix incorrect DRAW_FRAMEBUFFER name when blitting

* bind the widget surface fbo before painting egui

* make composite_specific_target take CompositeTarget, not Option

* compositing: remove cargo feature “gl”

* capitalise FBO in bind log message

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* capitalise FBO in drop log message

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* rename RenderTargetInfo fields and use OnceCell for next field

* rename RenderTargetInfo.read to read_back_from_gpu

* document servo_framebuffer_id in Minibrowser::update

* rename needs_fbo to use_offscreen_framebuffer

* capitalise FBO in unbind log message

* clarify the purpose of Minibrowser::on_event

* fix unused_must_use warning

* reduce nesting in Minibrowser::update

* use implicit format argument in panic

* store Minibrowser.widget_surface_fbo as glow type

* explain why servo_framebuffer_id is None in first call site

* rename output_framebuffer_id to offscreen_framebuffer_id

---------

Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Delan Azabani 2023-12-13 10:49:25 +08:00 committed by GitHub
parent 97e6c72f57
commit 17f3c45d4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 498 additions and 273 deletions

View file

@ -13,18 +13,18 @@ path = "lib.rs"
[features]
default = []
gl = ["gleam", "pixels"]
multiview = []
[dependencies]
canvas = { path = "../canvas" }
cfg-if = { workspace = true }
compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true }
embedder_traits = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
gfx_traits = { workspace = true }
gleam = { workspace = true, optional = true }
gleam = { workspace = true }
image = { workspace = true }
ipc-channel = { workspace = true }
keyboard-types = { workspace = true }
@ -33,7 +33,7 @@ log = { workspace = true }
msg = { workspace = true }
net_traits = { workspace = true }
num-traits = { workspace = true }
pixels = { path = "../pixels", optional = true }
pixels = { path = "../pixels" }
profile_traits = { workspace = true }
script_traits = { workspace = true }
servo_config = { path = "../config" }

View file

@ -2,6 +2,7 @@
* 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/. */
use std::cell::OnceCell;
use std::collections::HashMap;
use std::env;
use std::fs::{create_dir_all, File};
@ -21,17 +22,15 @@ use embedder_traits::Cursor;
use euclid::{Point2D, Rect, Scale, Transform3D, Vector2D};
use fnv::{FnvHashMap, FnvHashSet};
use gfx_traits::{Epoch, FontData, WebRenderEpochToU16};
#[cfg(feature = "gl")]
use image::{DynamicImage, ImageFormat};
use ipc_channel::ipc;
use libc::c_void;
use log::{debug, error, info, warn};
use log::{debug, error, info, trace, warn};
use msg::constellation_msg::{
PipelineId, PipelineIndex, PipelineNamespaceId, TopLevelBrowsingContextId,
};
use net_traits::image::base::Image;
use net_traits::image_cache::CorsStatus;
#[cfg(feature = "gl")]
use pixels::PixelFormat;
use profile_traits::time::{self as profile_time, profile, ProfilerCategory};
use script_traits::compositor::{HitTestInfo, ScrollTree};
@ -57,13 +56,12 @@ use webrender_api::{
};
use webrender_surfman::WebrenderSurfman;
#[cfg(feature = "gl")]
use crate::gl;
use crate::gl::RenderTargetInfo;
use crate::touch::{TouchAction, TouchHandler};
use crate::windowing::{
self, EmbedderCoordinates, MouseWindowEvent, WebRenderDebugOption, WindowMethods,
};
use crate::InitialCompositorState;
use crate::{gl, InitialCompositorState};
#[derive(Debug, PartialEq)]
enum UnableToComposite {
@ -225,7 +223,18 @@ pub struct IOCompositor<Window: WindowMethods + ?Sized> {
/// Current cursor position.
cursor_pos: DevicePoint,
output_file: Option<String>,
/// Offscreen framebuffer object to render our next frame to.
/// We use this and `prev_offscreen_framebuffer` for double buffering when compositing to
/// [`CompositeTarget::Fbo`].
next_offscreen_framebuffer: OnceCell<gl::RenderTargetInfo>,
/// Offscreen framebuffer object for our most-recently-completed frame.
/// We use this and `next_offscreen_framebuffer` for double buffering when compositing to
/// [`CompositeTarget::Fbo`].
prev_offscreen_framebuffer: Option<gl::RenderTargetInfo>,
/// Whether to invalidate `prev_offscreen_framebuffer` at the end of the next frame.
invalidate_prev_offscreen_framebuffer: bool,
is_running_problem_test: bool,
@ -339,33 +348,32 @@ impl PipelineDetails {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum CompositeTarget {
/// Normal composition to a window
#[derive(Clone, Debug, PartialEq)]
pub enum CompositeTarget {
/// Draw directly to a window.
Window,
/// Compose as normal, but also return a PNG of the composed output
WindowAndPng,
/// Draw to an offscreen OpenGL framebuffer object, which can be retrieved once complete at
/// [`IOCompositor::offscreen_framebuffer_id`].
Fbo,
/// Compose to a PNG, write it to disk, and then exit the browser (used for reftests)
PngFile,
/// Draw to an uncompressed image in shared memory.
SharedMemory,
/// Draw to a PNG file on disk, then exit the browser (for reftests).
PngFile(Rc<String>),
}
impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
fn new(
window: Rc<Window>,
state: InitialCompositorState,
output_file: Option<String>,
composite_target: CompositeTarget,
is_running_problem_test: bool,
exit_after_load: bool,
convert_mouse_to_touch: bool,
top_level_browsing_context_id: TopLevelBrowsingContextId,
) -> Self {
let composite_target = match output_file {
Some(_) => CompositeTarget::PngFile,
None => CompositeTarget::Window,
};
IOCompositor {
embedder_coordinates: window.get_coordinates(),
window,
@ -401,7 +409,9 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
pending_paint_metrics: HashMap::new(),
cursor: Cursor::None,
cursor_pos: DevicePoint::new(0.0, 0.0),
output_file,
next_offscreen_framebuffer: OnceCell::new(),
prev_offscreen_framebuffer: None,
invalidate_prev_offscreen_framebuffer: false,
is_running_problem_test,
exit_after_load,
convert_mouse_to_touch,
@ -413,7 +423,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
pub fn create(
window: Rc<Window>,
state: InitialCompositorState,
output_file: Option<String>,
composite_target: CompositeTarget,
is_running_problem_test: bool,
exit_after_load: bool,
convert_mouse_to_touch: bool,
@ -422,7 +432,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
let mut compositor = IOCompositor::new(
window,
state,
output_file,
composite_target,
is_running_problem_test,
exit_after_load,
convert_mouse_to_touch,
@ -525,7 +535,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
},
(CompositorMsg::CreatePng(rect, reply), ShutdownState::NotShuttingDown) => {
let res = self.composite_specific_target(CompositeTarget::WindowAndPng, rect);
let res = self.composite_specific_target(CompositeTarget::SharedMemory, rect);
if let Err(ref e) = res {
info!("Error retrieving PNG: {:?}", e);
}
@ -592,7 +602,9 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
(CompositorMsg::LoadComplete(_), ShutdownState::NotShuttingDown) => {
// If we're painting in headless mode, schedule a recomposite.
if self.output_file.is_some() || self.exit_after_load {
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
self.exit_after_load
{
self.composite_if_necessary(CompositingReason::Headless);
}
},
@ -1050,7 +1062,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
pub fn on_resize_window_event(&mut self) -> bool {
debug!("compositor resize requested");
trace!("Compositor resize requested");
let old_coords = self.embedder_coordinates;
self.embedder_coordinates = self.window.get_coordinates();
@ -1060,6 +1072,13 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
self.update_zoom_transform();
}
// If the framebuffer size has changed, invalidate the current framebuffer object, and mark
// the last framebuffer object as needing to be invalidated at the end of the next frame.
if self.embedder_coordinates.framebuffer != old_coords.framebuffer {
self.next_offscreen_framebuffer = OnceCell::new();
self.invalidate_prev_offscreen_framebuffer = true;
}
if self.embedder_coordinates.viewport == old_coords.viewport {
return false;
}
@ -1491,7 +1510,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
if self.output_file.is_some() {
if matches!(self.composite_target, CompositeTarget::PngFile(_)) {
return Scale::new(1.0);
}
self.embedder_coordinates.hidpi_factor
@ -1636,10 +1655,11 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
pub fn composite(&mut self) {
let target = self.composite_target;
match self.composite_specific_target(target, None) {
match self.composite_specific_target(self.composite_target.clone(), None) {
Ok(_) => {
if self.output_file.is_some() || self.exit_after_load {
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
self.exit_after_load
{
println!("Shutting down the Constellation after generating an output file or exit flag specified");
self.start_shutting_down();
}
@ -1656,11 +1676,10 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
}
/// Composite either to the screen or to a png image or both.
/// Returns Ok if composition was performed or Err if it was not possible to composite
/// for some reason. If CompositeTarget is Window or Png no image data is returned;
/// in the latter case the image is written directly to a file. If CompositeTarget
/// is WindowAndPng Ok(Some(png::Image)) is returned.
/// Composite to the given target if any, or the current target otherwise.
/// Returns Ok if composition was performed or Err if it was not possible to composite for some
/// reason. When the target is [CompositeTarget::SharedMemory], the image is read back from the
/// GPU and returned as Ok(Some(png::Image)), otherwise we return Ok(None).
fn composite_specific_target(
&mut self,
target: CompositeTarget,
@ -1680,23 +1699,16 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
self.assert_no_gl_error();
// Bind the webrender framebuffer
let framebuffer_object = self
.webrender_surfman
.context_surface_info()
.unwrap_or(None)
.map(|info| info.framebuffer_object)
.unwrap_or(0);
self.webrender_gl
.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_object);
self.assert_gl_framebuffer_complete();
self.webrender.update();
let wait_for_stable_image = match target {
CompositeTarget::WindowAndPng | CompositeTarget::PngFile => true,
CompositeTarget::Window => self.exit_after_load,
};
let wait_for_stable_image = matches!(
target,
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_)
) || self.exit_after_load;
let use_offscreen_framebuffer = matches!(
target,
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_) | CompositeTarget::Fbo
);
if wait_for_stable_image {
// The current image may be ready to output. However, if there are animations active,
@ -1713,25 +1725,35 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
}
let rt_info = match target {
#[cfg(feature = "gl")]
CompositeTarget::Window => gl::RenderTargetInfo::default(),
#[cfg(feature = "gl")]
CompositeTarget::WindowAndPng | CompositeTarget::PngFile => gl::initialize_png(
&*self.webrender_gl,
FramebufferUintLength::new(size.width),
FramebufferUintLength::new(size.height),
),
#[cfg(not(feature = "gl"))]
_ => (),
};
if use_offscreen_framebuffer {
self.next_offscreen_framebuffer
.get_or_init(|| {
RenderTargetInfo::new(
self.webrender_gl.clone(),
FramebufferUintLength::new(size.width),
FramebufferUintLength::new(size.height),
)
})
.bind();
} else {
// Bind the webrender framebuffer
let framebuffer_object = self
.webrender_surfman
.context_surface_info()
.unwrap_or(None)
.map(|info| info.framebuffer_object)
.unwrap_or(0);
self.webrender_gl
.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_object);
self.assert_gl_framebuffer_complete();
}
profile(
ProfilerCategory::Compositing,
None,
self.time_profiler_chan.clone(),
|| {
debug!("compositor: compositing");
trace!("Compositing");
let size =
DeviceIntSize::from_untyped(self.embedder_coordinates.framebuffer.to_untyped());
@ -1781,29 +1803,47 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
}
let (x, y, width, height) = match rect {
Some(rect) => {
let rect = self.device_pixels_per_page_px().transform_rect(&rect);
let (x, y, width, height) = if let Some(rect) = rect {
let rect = self.device_pixels_per_page_px().transform_rect(&rect);
let x = rect.origin.x as i32;
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
let y = (size.height as f32 - rect.origin.y - rect.size.height) as i32;
let w = rect.size.width as u32;
let h = rect.size.height as u32;
let x = rect.origin.x as i32;
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
let y = (size.height as f32 - rect.origin.y - rect.size.height) as i32;
let w = rect.size.width as u32;
let h = rect.size.height as u32;
(x, y, w, h)
},
None => (0, 0, size.width, size.height),
(x, y, w, h)
} else {
(0, 0, size.width, size.height)
};
let rv = match target {
CompositeTarget::Window => None,
#[cfg(feature = "gl")]
CompositeTarget::WindowAndPng => {
let img = gl::draw_img(
&*self.webrender_gl,
rt_info,
CompositeTarget::Fbo => {
self.next_offscreen_framebuffer
.get()
.expect("Guaranteed by needs_fbo")
.unbind();
if self.invalidate_prev_offscreen_framebuffer {
// Do not reuse the last render target as the new current render target.
self.prev_offscreen_framebuffer = None;
self.invalidate_prev_offscreen_framebuffer = false;
}
let old_prev = self.prev_offscreen_framebuffer.take();
self.prev_offscreen_framebuffer = self.next_offscreen_framebuffer.take();
if let Some(old_prev) = old_prev {
let result = self.next_offscreen_framebuffer.set(old_prev);
debug_assert!(result.is_ok(), "Guaranteed by take");
}
None
},
CompositeTarget::SharedMemory => {
let render_target_info = self
.next_offscreen_framebuffer
.take()
.expect("Guaranteed by needs_fbo");
let img = render_target_info.read_back_from_gpu(
x,
y,
FramebufferUintLength::new(width),
@ -1818,39 +1858,33 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
cors_status: CorsStatus::Safe,
})
},
#[cfg(feature = "gl")]
CompositeTarget::PngFile => {
let gl = &*self.webrender_gl;
CompositeTarget::PngFile(path) => {
profile(
ProfilerCategory::ImageSaving,
None,
self.time_profiler_chan.clone(),
|| match self.output_file.as_ref() {
Some(path) => match File::create(path) {
Ok(mut file) => {
let img = gl::draw_img(
gl,
rt_info,
x,
y,
FramebufferUintLength::new(width),
FramebufferUintLength::new(height),
);
let dynamic_image = DynamicImage::ImageRgb8(img);
if let Err(e) = dynamic_image.write_to(&mut file, ImageFormat::Png)
{
error!("Failed to save {} ({}).", path, e);
}
},
Err(e) => error!("Failed to create {} ({}).", path, e),
|| match File::create(&*path) {
Ok(mut file) => {
let render_target_info = self
.next_offscreen_framebuffer
.take()
.expect("Guaranteed by needs_fbo");
let img = render_target_info.read_back_from_gpu(
x,
y,
FramebufferUintLength::new(width),
FramebufferUintLength::new(height),
);
let dynamic_image = DynamicImage::ImageRgb8(img);
if let Err(e) = dynamic_image.write_to(&mut file, ImageFormat::Png) {
error!("Failed to save {} ({}).", path, e);
}
},
None => error!("No file specified."),
Err(e) => error!("Failed to create {} ({}).", path, e),
},
);
None
},
#[cfg(not(feature = "gl"))]
_ => None,
};
// Nottify embedder that servo is ready to present.
@ -1871,6 +1905,14 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
Ok(rv)
}
/// Return the OpenGL framebuffer name of the most-recently-completed frame when compositing to
/// [`CompositeTarget::Fbo`], or None otherwise.
pub fn offscreen_framebuffer_id(&self) -> Option<gleam::gl::GLuint> {
self.prev_offscreen_framebuffer
.as_ref()
.map(|info| info.framebuffer_id())
}
pub fn present(&mut self) {
if let Err(err) = self.webrender_surfman.present() {
warn!("Failed to present surface: {:?}", err);

View file

@ -2,125 +2,149 @@
* 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/. */
use gleam::gl;
use std::rc::Rc;
use gleam::gl::{self, Gl};
use image::RgbImage;
use log::trace;
use servo_geometry::FramebufferUintLength;
#[derive(Default)]
pub struct RenderTargetInfo {
gl: Rc<dyn Gl>,
framebuffer_ids: Vec<gl::GLuint>,
renderbuffer_ids: Vec<gl::GLuint>,
texture_ids: Vec<gl::GLuint>,
}
pub fn initialize_png(
gl: &dyn gl::Gl,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RenderTargetInfo {
let framebuffer_ids = gl.gen_framebuffers(1);
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
impl RenderTargetInfo {
pub fn new(
gl: Rc<dyn Gl>,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> Self {
let framebuffer_ids = gl.gen_framebuffers(1);
gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
trace!("Configuring fbo {}", framebuffer_ids[0]);
let texture_ids = gl.gen_textures(1);
gl.bind_texture(gl::TEXTURE_2D, texture_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::RGB as gl::GLint,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
0,
gl::RGB,
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.tex_image_2d(
gl::TEXTURE_2D,
0,
gl::RGB as gl::GLint,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
0,
gl::RGB,
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.framebuffer_texture_2d(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture_ids[0],
0,
);
gl.bind_texture(gl::TEXTURE_2D, 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,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
);
gl.framebuffer_renderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
depth_rb,
);
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,
width.get() as gl::GLsizei,
height.get() as gl::GLsizei,
);
gl.framebuffer_renderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
depth_rb,
);
Self {
gl,
framebuffer_ids,
renderbuffer_ids,
texture_ids,
}
}
RenderTargetInfo {
framebuffer_ids,
renderbuffer_ids,
texture_ids,
pub fn framebuffer_id(&self) -> gl::GLuint {
*self.framebuffer_ids.first().expect("Guaranteed by new")
}
pub fn bind(&self) {
trace!("Binding FBO {}", self.framebuffer_id());
self.gl
.bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer_id());
}
pub fn unbind(&self) {
trace!("Unbinding FBO {}", self.framebuffer_id());
self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
}
pub fn read_back_from_gpu(
self,
x: i32,
y: i32,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RgbImage {
let width = width.get() as usize;
let height = height.get() as usize;
// 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.
self.gl.bind_vertex_array(0);
let mut pixels = self.gl.read_pixels(
x,
y,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGB,
gl::UNSIGNED_BYTE,
);
// flip image vertically (texture is upside down)
let orig_pixels = pixels.clone();
let stride = width * 3;
for y in 0..height {
let dst_start = y * stride;
let src_start = (height - y - 1) * stride;
let src_slice = &orig_pixels[src_start..src_start + stride];
(&mut pixels[dst_start..dst_start + stride]).clone_from_slice(&src_slice[..stride]);
}
RgbImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
}
}
pub fn draw_img(
gl: &dyn gl::Gl,
render_target_info: RenderTargetInfo,
x: i32,
y: i32,
width: FramebufferUintLength,
height: FramebufferUintLength,
) -> RgbImage {
let width = width.get() as usize;
let height = height.get() as usize;
// 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(
x,
y,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGB,
gl::UNSIGNED_BYTE,
);
gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
gl.delete_textures(&render_target_info.texture_ids);
gl.delete_renderbuffers(&render_target_info.renderbuffer_ids);
gl.delete_framebuffers(&render_target_info.framebuffer_ids);
// flip image vertically (texture is upside down)
let orig_pixels = pixels.clone();
let stride = width * 3;
for y in 0..height {
let dst_start = y * stride;
let src_start = (height - y - 1) * stride;
let src_slice = &orig_pixels[src_start..src_start + stride];
(&mut pixels[dst_start..dst_start + stride]).clone_from_slice(&src_slice[..stride]);
impl Drop for RenderTargetInfo {
fn drop(&mut self) {
trace!("Dropping FBO {}", self.framebuffer_id());
self.unbind();
self.gl.delete_textures(&self.texture_ids);
self.gl.delete_renderbuffers(&self.renderbuffer_ids);
self.gl.delete_framebuffers(&self.framebuffer_ids);
}
RgbImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
}

View file

@ -13,10 +13,9 @@ use webrender::RenderApi;
use webrender_api::DocumentId;
use webrender_surfman::WebrenderSurfman;
pub use crate::compositor::{IOCompositor, ShutdownState};
pub use crate::compositor::{CompositeTarget, IOCompositor, ShutdownState};
mod compositor;
#[cfg(feature = "gl")]
mod gl;
mod touch;
pub mod windowing;