Compare commits

...

15 Commits

Author SHA1 Message Date
Delan Azabani
f6bc3ad8ad merge from main 2023-12-07 17:03:15 +08:00
Delan Azabani
59979f391c bind the widget surface fbo before painting egui 2023-12-07 16:50:04 +08:00
Delan Azabani
f7b423faa0 merge from main 2023-12-06 18:21:37 +08:00
Delan Azabani
44ecfb6a48 fix incorrect DRAW_FRAMEBUFFER name when blitting 2023-12-06 16:45:03 +08:00
Delan Azabani
a685ab3f05 unset invalidate_last_render_target after invalidating 2023-11-27 17:17:23 +08:00
Delan Azabani
2e281521dd fix flickering around edges after resizing window 2023-11-23 18:40:09 +08:00
Delan Azabani
7aa0007302 clean up compositing::gl 2023-11-23 18:17:24 +08:00
Delan Azabani
7ecd362d9f avoid destroying OpenGL resources unless resizing window 2023-11-23 18:09:06 +08:00
Delan Azabani
0930995905 fix mouse input for browser being consumed by egui 2023-11-23 16:54:33 +08:00
Delan Azabani
ce06439bdb clear to transparent, to avoid pink artifacts 2023-11-23 15:52:55 +08:00
Delan Azabani
c956d09fb5 remove dark CentralPanel border covering edges of viewport 2023-11-23 15:51:19 +08:00
Delan Azabani
1f07d7bd76 update doc comments 2023-11-23 15:24:55 +08:00
Delan Azabani
3ea0ac65e3 fix compile errors (in theory) when gl crate feature disabled 2023-11-22 16:07:04 +08:00
Delan Azabani
b422d3c9a0 shared memory case never actually rendered to backbuffer 2023-11-22 15:33:30 +08:00
Delan Azabani
170fd33f79 Offscreen rendering 2023-11-22 14:57:12 +08:00
11 changed files with 507 additions and 287 deletions

1
Cargo.lock generated
View File

@@ -919,6 +919,7 @@ name = "compositing"
version = "0.0.1"
dependencies = [
"canvas",
"cfg-if 1.0.0",
"compositing_traits",
"crossbeam-channel",
"embedder_traits",

View File

@@ -17,6 +17,7 @@ gl = ["gleam", "pixels"]
[dependencies]
canvas = { path = "../canvas" }
cfg-if = { workspace = true }
compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true }
embedder_traits = { workspace = true }

View File

@@ -6,11 +6,13 @@ use std::collections::HashMap;
use std::env;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::mem::swap;
use std::num::NonZeroU32;
use std::rc::Rc;
use std::time::{SystemTime, UNIX_EPOCH};
use canvas::canvas_paint_thread::ImageUpdate;
use cfg_if::cfg_if;
use compositing_traits::{
CanvasToCompositorMsg, CompositingReason, CompositionPipeline, CompositorMsg,
CompositorReceiver, ConstellationMsg, FontToCompositorMsg, ForwardedToCompositorMsg,
@@ -25,7 +27,7 @@ use gfx_traits::{Epoch, FontData, WebRenderEpochToU16};
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,
};
@@ -59,6 +61,7 @@ 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,
@@ -225,7 +228,14 @@ pub struct IOCompositor<Window: WindowMethods + ?Sized> {
/// Current cursor position.
cursor_pos: DevicePoint,
output_file: Option<String>,
/// Offscreen framebuffer object to render to.
current_render_target: Option<gl::RenderTargetInfo>,
/// Offscreen framebuffer object that our last frame was rendered to.
last_render_target: Option<gl::RenderTargetInfo>,
/// Whether to invalidate the last render target at the end of the next frame.
invalidate_last_render_target: bool,
is_running_problem_test: bool,
@@ -339,33 +349,31 @@ 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 ([IOCompositor::output_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,
current_render_target: None,
last_render_target: None,
invalidate_last_render_target: 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(Some(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.current_render_target = None;
self.invalidate_last_render_target = 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(None, 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,14 +1676,13 @@ 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,
target_override: Option<CompositeTarget>,
rect: Option<Rect<f32, CSSPixel>>,
) -> Result<Option<Image>, UnableToComposite> {
if self.waiting_on_present {
@@ -1680,23 +1699,17 @@ 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 target = target_override.unwrap_or_else(|| self.composite_target.clone());
let wait_for_stable_image = matches!(
target,
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_)
) || self.exit_after_load;
let needs_fbo = 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,17 +1726,33 @@ 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"))]
_ => (),
cfg_if! {
if #[cfg(feature = "gl")] {
if needs_fbo {
if self.current_render_target.is_none() {
self.current_render_target = Some(RenderTargetInfo::new(
self.webrender_gl.clone(),
FramebufferUintLength::new(size.width),
FramebufferUintLength::new(size.height),
));
}
self.current_render_target
.as_ref()
.expect("Initialised above")
.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(
@@ -1731,7 +1760,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
None,
self.time_profiler_chan.clone(),
|| {
debug!("compositor: compositing");
trace!("Compositing");
let size =
DeviceIntSize::from_untyped(self.embedder_coordinates.framebuffer.to_untyped());
@@ -1781,77 +1810,82 @@ 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,
x,
y,
FramebufferUintLength::new(width),
FramebufferUintLength::new(height),
);
Some(Image {
width: img.width(),
height: img.height(),
format: PixelFormat::RGB8,
bytes: ipc::IpcSharedMemory::from_bytes(&*img),
id: None,
cors_status: CorsStatus::Safe,
})
},
#[cfg(feature = "gl")]
CompositeTarget::PngFile => {
let gl = &*self.webrender_gl;
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),
},
None => error!("No file specified."),
cfg_if! {
if #[cfg(feature = "gl")] {
let rv = match target {
CompositeTarget::Window => None,
CompositeTarget::Fbo => {
self.current_render_target.as_ref().expect("Guaranteed by needs_fbo").unbind();
if self.invalidate_last_render_target {
// Do not reuse the last render target as the new current render target.
self.last_render_target = None;
self.invalidate_last_render_target = false;
}
swap(&mut self.current_render_target, &mut self.last_render_target);
None
},
);
None
},
#[cfg(not(feature = "gl"))]
_ => None,
};
CompositeTarget::SharedMemory => {
let render_target_info = self.current_render_target.take().expect("Guaranteed by needs_fbo");
let img = render_target_info.read(
x,
y,
FramebufferUintLength::new(width),
FramebufferUintLength::new(height),
);
Some(Image {
width: img.width(),
height: img.height(),
format: PixelFormat::RGB8,
bytes: ipc::IpcSharedMemory::from_bytes(&*img),
id: None,
cors_status: CorsStatus::Safe,
})
},
CompositeTarget::PngFile(path) => {
profile(
ProfilerCategory::ImageSaving,
None,
self.time_profiler_chan.clone(),
|| match File::create(&*path) {
Ok(mut file) => {
let render_target_info = self.current_render_target.take()
.expect("Guaranteed by needs_fbo");
let img = render_target_info.read(
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),
},
);
None
},
};
} else {
let rv = None;
}
}
// Nottify embedder that servo is ready to present.
// Embedder should call `present` to tell compositor to continue rendering.
@@ -1871,6 +1905,12 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
Ok(rv)
}
pub fn output_framebuffer_id(&self) -> Option<gleam::gl::GLuint> {
self.last_render_target
.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(
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,7 +13,7 @@ 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")]

View File

@@ -30,7 +30,7 @@ use canvas::canvas_paint_thread::{self, CanvasPaintThread};
use canvas::WebGLComm;
use canvas_traits::webgl::WebGLThreads;
use compositing::windowing::{EmbedderEvent, EmbedderMethods, WindowMethods};
use compositing::{IOCompositor, InitialCompositorState, ShutdownState};
use compositing::{CompositeTarget, IOCompositor, InitialCompositorState, ShutdownState};
use compositing_traits::{
CanvasToCompositorMsg, CompositingReason, CompositorMsg, CompositorProxy, CompositorReceiver,
ConstellationMsg, FontToCompositorMsg, ForwardedToCompositorMsg,
@@ -224,6 +224,7 @@ where
mut embedder: Box<dyn EmbedderMethods>,
window: Rc<Window>,
user_agent: Option<String>,
composite_target: CompositeTarget,
) -> InitializedServo<Window> {
// Global configuration options, parsed from the command line.
let opts = opts::get();
@@ -447,6 +448,12 @@ where
}
}
let composite_target = if let Some(path) = opts.output_file.clone() {
CompositeTarget::PngFile(path.into())
} else {
composite_target
};
// The compositor coordinates with the client window to create the final
// rendered page and display it somewhere.
let compositor = IOCompositor::create(
@@ -464,7 +471,7 @@ where
webrender_gl,
webxr_main_thread,
},
opts.output_file.clone(),
composite_target,
opts.is_running_problem_test,
opts.exit_after_load,
opts.debug.convert_mouse_to_touch,
@@ -769,6 +776,10 @@ where
pub fn recomposite(&mut self) {
self.compositor.composite();
}
pub fn output_framebuffer_id(&self) -> Option<u32> {
self.compositor.output_framebuffer_id()
}
}
fn create_embedder_channel(

View File

@@ -13,6 +13,7 @@ use std::{env, fs};
use gleam::gl;
use log::{info, trace, warn};
use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget;
use servo::config::opts;
use servo::servo_config::pref;
use servo::Servo;
@@ -122,8 +123,8 @@ impl App {
}
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(window.winit_window().unwrap(), "init");
window.set_toolbar_height(minibrowser.toolbar_height.get());
minibrowser.update(window.winit_window().unwrap(), None, "init");
window.set_toolbar_height(minibrowser.toolbar_height);
}
// Whether or not to recomposite during the next RedrawRequested event.
@@ -185,7 +186,17 @@ impl App {
// Implements embedder methods, used by libservo and constellation.
let embedder = Box::new(EmbedderCallbacks::new(ev_waker.clone(), xr_discovery));
let servo_data = Servo::new(embedder, window.clone(), user_agent.clone());
let composite_target = if app.minibrowser.is_some() {
CompositeTarget::Fbo
} else {
CompositeTarget::Window
};
let servo_data = Servo::new(
embedder,
window.clone(),
user_agent.clone(),
composite_target,
);
let mut servo = servo_data.servo;
servo.handle_events(vec![EmbedderEvent::NewBrowser(
@@ -217,7 +228,11 @@ impl App {
app.servo.as_mut().unwrap().recomposite();
}
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(window.winit_window().unwrap(), "RedrawRequested");
minibrowser.update(
window.winit_window().unwrap(),
app.servo.as_ref().unwrap().output_framebuffer_id(),
"RedrawRequested",
);
minibrowser.paint(window.winit_window().unwrap());
}
app.servo.as_mut().unwrap().present();
@@ -252,7 +267,7 @@ impl App {
window.winit_window().unwrap().request_redraw();
},
winit::event::Event::WindowEvent { ref event, .. } => {
let response = minibrowser.context.on_event(&event);
let response = minibrowser.on_event(&event);
if response.repaint {
// Request a winit redraw event, so we can recomposite, update and paint
// the minibrowser, and present the new frame.
@@ -306,6 +321,7 @@ impl App {
// redraw, doing so would delay the location update by two frames.
minibrowser.update(
window.winit_window().unwrap(),
app.servo.as_ref().unwrap().output_framebuffer_id(),
"update_location_in_toolbar",
);
}
@@ -323,6 +339,7 @@ impl App {
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(
window.winit_window().unwrap(),
app.servo.as_ref().unwrap().output_framebuffer_id(),
"PumpResult::Present::Immediate",
);
minibrowser.paint(window.winit_window().unwrap());

View File

@@ -0,0 +1,15 @@
/* 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/. */
use euclid::{Point2D, Size2D};
use servo::style_traits::DevicePixel;
use winit::dpi::{PhysicalPosition, PhysicalSize};
pub fn winit_size_to_euclid_size<T>(size: PhysicalSize<T>) -> Size2D<T, DevicePixel> {
Size2D::new(size.width, size.height)
}
pub fn winit_position_to_euclid_point<T>(position: PhysicalPosition<T>) -> Point2D<T, DevicePixel> {
Point2D::new(position.x, position.y)
}

View File

@@ -41,6 +41,7 @@ use winit::event::{
use winit::window::Icon;
use crate::events_loop::{EventsLoop, WakerEvent};
use crate::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size};
use crate::keyutils::keyboard_event_from_winit;
use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
@@ -519,14 +520,6 @@ impl WindowPortsMethods for Window {
}
}
fn winit_size_to_euclid_size<T>(size: PhysicalSize<T>) -> Size2D<T, DevicePixel> {
Size2D::new(size.width, size.height)
}
fn winit_position_to_euclid_point<T>(position: PhysicalPosition<T>) -> Point2D<T, DevicePixel> {
Point2D::new(position.x, position.y)
}
impl WindowMethods for Window {
fn get_coordinates(&self) -> EmbedderCoordinates {
let window_size = winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32();

View File

@@ -36,6 +36,7 @@ cfg_if::cfg_if! {
mod egui_glue;
mod embedder;
mod events_loop;
mod geometry;
mod headed_window;
mod headless_window;
mod keyutils;

View File

@@ -3,11 +3,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, RefCell};
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::Instant;
use egui::{Key, Modifiers, TopBottomPanel};
use euclid::Length;
use egui::{CentralPanel, Frame, InnerResponse, Key, Modifiers, PaintCallback, TopBottomPanel};
use egui_glow::CallbackFn;
use egui_winit::EventResponse;
use euclid::{Length, Point2D, Scale};
use gleam::gl;
use glow::NativeFramebuffer;
use log::{trace, warn};
use servo::compositing::windowing::EmbedderEvent;
use servo::msg::constellation_msg::TraversalDirection;
@@ -18,14 +23,17 @@ use servo::webrender_surfman::WebrenderSurfman;
use crate::browser::Browser;
use crate::egui_glue::EguiGlow;
use crate::events_loop::EventsLoop;
use crate::geometry::winit_position_to_euclid_point;
use crate::parser::location_bar_input_to_url;
use crate::window_trait::WindowPortsMethods;
pub struct Minibrowser {
pub context: EguiGlow,
pub event_queue: RefCell<Vec<MinibrowserEvent>>,
pub toolbar_height: Cell<Length<f32, DeviceIndependentPixel>>,
pub toolbar_height: Length<f32, DeviceIndependentPixel>,
widget_surface_fbo: gl::GLuint,
last_update: Instant,
last_mouse_position: Option<Point2D<f32, DeviceIndependentPixel>>,
location: RefCell<String>,
/// Whether the location has been edited by the user without clicking Go.
@@ -56,18 +64,62 @@ impl Minibrowser {
.egui_ctx
.set_pixels_per_point(window.hidpi_factor().get());
let widget_surface_fbo = match webrender_surfman.context_surface_info() {
Ok(Some(info)) => info.framebuffer_object,
Ok(None) => panic!("Failed to get widget surface info from surfman!"),
Err(error) => panic!(
"Failed to get widget surface info from surfman! {:?}",
error
),
};
Self {
context,
event_queue: RefCell::new(vec![]),
toolbar_height: Default::default(),
widget_surface_fbo,
last_update: Instant::now(),
last_mouse_position: None,
location: RefCell::new(initial_url.to_string()),
location_dirty: false.into(),
}
}
/// Preprocess the given [winit::event::WindowEvent], returning unconsumed for mouse events in
/// the Servo browser rect.
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse {
let mut result = self.context.on_event(event);
result.consumed &= match event {
winit::event::WindowEvent::CursorMoved { position, .. } => {
let scale = Scale::<_, DeviceIndependentPixel, _>::new(
self.context.egui_ctx.pixels_per_point(),
);
self.last_mouse_position =
Some(winit_position_to_euclid_point(*position).to_f32() / scale);
self.last_mouse_position
.map_or(false, |p| self.is_in_browser_rect(p))
},
winit::event::WindowEvent::MouseWheel { .. } |
winit::event::WindowEvent::MouseInput { .. } => self
.last_mouse_position
.map_or(false, |p| self.is_in_browser_rect(p)),
_ => true,
};
result
}
/// Return true iff the given position is in the Servo browser rect.
fn is_in_browser_rect(&self, position: Point2D<f32, DeviceIndependentPixel>) -> bool {
position.y < self.toolbar_height.get()
}
/// Update the minibrowser, but dont paint.
pub fn update(&mut self, window: &winit::window::Window, reason: &'static str) {
pub fn update(
&mut self,
window: &winit::window::Window,
servo_framebuffer_id: Option<gl::GLuint>,
reason: &'static str,
) {
let now = Instant::now();
trace!(
"{:?} since last update ({})",
@@ -78,62 +130,127 @@ impl Minibrowser {
context,
event_queue,
toolbar_height,
widget_surface_fbo,
last_update,
location,
location_dirty,
..
} = self;
let our_fbo = *widget_surface_fbo;
let _duration = context.run(window, |ctx| {
TopBottomPanel::top("toolbar").show(ctx, |ui| {
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
if ui.button("back").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Back);
}
if ui.button("forward").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Forward);
}
let InnerResponse { inner: height, .. } =
TopBottomPanel::top("toolbar").show(ctx, |ui| {
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
if ui.button("back").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Back);
}
if ui.button("forward").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Forward);
}
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if ui.button("go").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
}
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if ui.button("go").clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
}
let location_field = ui.add_sized(
ui.available_size(),
egui::TextEdit::singleline(&mut *location.borrow_mut()),
);
let location_field = ui.add_sized(
ui.available_size(),
egui::TextEdit::singleline(&mut *location.borrow_mut()),
);
if location_field.changed() {
location_dirty.set(true);
}
if ui.input(|i| {
i.clone().consume_key(Modifiers::COMMAND, Key::L)
}) {
location_field.request_focus();
}
if location_field.lost_focus() &&
ui.input(|i| i.clone().key_pressed(Key::Enter))
{
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
}
},
);
},
);
ui.cursor().min.y
});
*toolbar_height = Length::new(height);
if location_field.changed() {
location_dirty.set(true);
}
if ui.input(|i| i.clone().consume_key(Modifiers::COMMAND, Key::L)) {
location_field.request_focus();
}
if location_field.lost_focus() &&
ui.input(|i| i.clone().key_pressed(Key::Enter))
{
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
}
},
);
},
);
});
CentralPanel::default()
.frame(Frame::none())
.show(ctx, |ui| {
let min = ui.cursor().min;
let size = ui.available_size();
let rect = egui::Rect::from_min_size(min, size);
ui.allocate_space(size);
if let Some(servo_fbo) = servo_framebuffer_id {
ui.painter().add(PaintCallback {
rect,
callback: Arc::new(CallbackFn::new(move |info, painter| {
use glow::HasContext as _;
let clip = info.viewport_in_pixels();
let x = clip.left_px as gl::GLint;
let y = clip.from_bottom_px as gl::GLint;
let width = clip.width_px as gl::GLsizei;
let height = clip.height_px as gl::GLsizei;
unsafe {
painter.gl().clear_color(0.0, 0.0, 0.0, 0.0);
painter.gl().scissor(x, y, width, height);
painter.gl().enable(gl::SCISSOR_TEST);
painter.gl().clear(gl::COLOR_BUFFER_BIT);
painter.gl().disable(gl::SCISSOR_TEST);
let servo_fbo =
NonZeroU32::new(servo_fbo).map(NativeFramebuffer);
let our_fbo = NonZeroU32::new(our_fbo).map(NativeFramebuffer);
painter
.gl()
.bind_framebuffer(gl::READ_FRAMEBUFFER, servo_fbo);
painter.gl().bind_framebuffer(gl::DRAW_FRAMEBUFFER, our_fbo);
painter.gl().blit_framebuffer(
x,
y,
x + width,
y + height,
x,
y,
x + width,
y + height,
gl::COLOR_BUFFER_BIT,
gl::NEAREST,
);
painter.gl().bind_framebuffer(gl::FRAMEBUFFER, our_fbo);
}
})),
});
}
});
toolbar_height.set(Length::new(ctx.used_rect().height()));
*last_update = now;
});
}
/// Paint the minibrowser, as of the last update.
pub fn paint(&mut self, window: &winit::window::Window) {
let our_fbo = NonZeroU32::new(self.widget_surface_fbo).map(NativeFramebuffer);
unsafe {
use glow::HasContext as _;
self.context
.painter
.gl()
.bind_framebuffer(gl::FRAMEBUFFER, our_fbo);
}
self.context.paint(window);
}