mirror of
https://github.com/servo/servo.git
synced 2025-08-08 23:15:33 +01:00
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:
parent
97e6c72f57
commit
17f3c45d4f
13 changed files with 498 additions and 273 deletions
|
@ -4,7 +4,7 @@ name = "servoshell"
|
|||
version = "0.0.1"
|
||||
authors = ["The Servo Project Developers"]
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
publish = false
|
||||
|
||||
|
|
|
@ -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,9 @@ impl App {
|
|||
}
|
||||
|
||||
if let Some(mut minibrowser) = app.minibrowser() {
|
||||
minibrowser.update(window.winit_window().unwrap(), "init");
|
||||
window.set_toolbar_height(minibrowser.toolbar_height.get());
|
||||
// Servo is not yet initialised, so there is no `servo_framebuffer_id`.
|
||||
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 +187,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 +229,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().offscreen_framebuffer_id(),
|
||||
"RedrawRequested",
|
||||
);
|
||||
minibrowser.paint(window.winit_window().unwrap());
|
||||
}
|
||||
app.servo.as_mut().unwrap().present();
|
||||
|
@ -252,7 +268,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 +322,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().offscreen_framebuffer_id(),
|
||||
"update_location_in_toolbar",
|
||||
);
|
||||
}
|
||||
|
@ -323,6 +340,7 @@ impl App {
|
|||
if let Some(mut minibrowser) = app.minibrowser() {
|
||||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
app.servo.as_ref().unwrap().offscreen_framebuffer_id(),
|
||||
"PumpResult::Present::Immediate",
|
||||
);
|
||||
minibrowser.paint(window.winit_window().unwrap());
|
||||
|
|
15
ports/servoshell/geometry.rs
Normal file
15
ports/servoshell/geometry.rs
Normal 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)
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,21 @@ 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>,
|
||||
|
||||
/// The framebuffer object name for the widget surface we should draw to, or None if our widget
|
||||
/// surface does not use a framebuffer object.
|
||||
widget_surface_fbo: Option<NativeFramebuffer>,
|
||||
|
||||
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 +68,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)) => NonZeroU32::new(info.framebuffer_object).map(NativeFramebuffer),
|
||||
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. This is needed because the CentralPanel we create for our webview
|
||||
/// would otherwise make egui report events in that area as consumed.
|
||||
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 don’t paint.
|
||||
pub fn update(&mut self, window: &winit::window::Window, reason: &'static str) {
|
||||
/// If `servo_framebuffer_id` is given, set up a paint callback to blit its contents to our
|
||||
/// CentralPanel when [`Minibrowser::paint`] is called.
|
||||
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 +134,125 @@ impl Minibrowser {
|
|||
context,
|
||||
event_queue,
|
||||
toolbar_height,
|
||||
widget_surface_fbo,
|
||||
last_update,
|
||||
location,
|
||||
location_dirty,
|
||||
..
|
||||
} = self;
|
||||
let widget_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);
|
||||
|
||||
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);
|
||||
|
||||
let Some(servo_fbo) = servo_framebuffer_id else { return };
|
||||
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);
|
||||
painter
|
||||
.gl()
|
||||
.bind_framebuffer(gl::READ_FRAMEBUFFER, servo_fbo);
|
||||
painter
|
||||
.gl()
|
||||
.bind_framebuffer(gl::DRAW_FRAMEBUFFER, widget_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, widget_fbo);
|
||||
}
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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) {
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
self.context
|
||||
.painter
|
||||
.gl()
|
||||
.bind_framebuffer(gl::FRAMEBUFFER, self.widget_surface_fbo);
|
||||
}
|
||||
self.context.paint(window);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue