mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
libservo: Expose an OffscreenRenderingContext
and use it for servoshell (#35465)
Create a new `RenderingContext` which is used to render to a `SurfmanRenderingContext`-related offscreen buffer. This allows having a temporary place to render Servo and then blitting the results to a subsection of the parent `RenderingContext`. The goal with this change is to remove the details of how servoshell renders from the `Compositor` and prepare for the compositor-per-WebView world. Co-authred-by: Ngo Iok Ui (Wu Yu Wei) <yuweiwu@pm.me> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
parent
d466688526
commit
6dce329acc
15 changed files with 655 additions and 608 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -8594,6 +8594,8 @@ dependencies = [
|
|||
"embedder_traits",
|
||||
"euclid",
|
||||
"gleam",
|
||||
"glow",
|
||||
"image",
|
||||
"ipc-channel",
|
||||
"libc",
|
||||
"log",
|
||||
|
|
|
@ -36,6 +36,7 @@ script_traits = { workspace = true }
|
|||
servo_config = { path = "../config" }
|
||||
servo_geometry = { path = "../geometry" }
|
||||
style_traits = { workspace = true }
|
||||
surfman = { workspace = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
webrender = { workspace = true }
|
||||
webrender_api = { workspace = true }
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* 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::hash_set::Iter;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
|
@ -24,7 +23,7 @@ use embedder_traits::{
|
|||
Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
|
||||
TouchEvent, TouchEventAction, TouchId,
|
||||
};
|
||||
use euclid::{Point2D, Rect, Scale, Transform3D, Vector2D};
|
||||
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||
|
@ -37,7 +36,7 @@ use script_traits::{
|
|||
AnimationState, AnimationTickType, ScriptThreadMessage, ScrollState, WindowSizeData,
|
||||
WindowSizeType,
|
||||
};
|
||||
use servo_geometry::{DeviceIndependentPixel, FramebufferUintLength};
|
||||
use servo_geometry::DeviceIndependentPixel;
|
||||
use style_traits::{CSSPixel, PinchZoomFactor};
|
||||
use webrender::{CaptureBits, RenderApi, Transaction};
|
||||
use webrender_api::units::{
|
||||
|
@ -57,11 +56,10 @@ use webrender_traits::{
|
|||
CompositorHitTestResult, CrossProcessCompositorMessage, ImageUpdate, UntrustedNodeAddress,
|
||||
};
|
||||
|
||||
use crate::gl::RenderTargetInfo;
|
||||
use crate::touch::{TouchAction, TouchHandler};
|
||||
use crate::webview::{UnknownWebView, WebView, WebViewAlreadyExists, WebViewManager};
|
||||
use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods};
|
||||
use crate::{gl, InitialCompositorState};
|
||||
use crate::InitialCompositorState;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum UnableToComposite {
|
||||
|
@ -191,19 +189,6 @@ pub struct IOCompositor {
|
|||
/// Current cursor position.
|
||||
cursor_pos: DevicePoint,
|
||||
|
||||
/// Offscreen framebuffer object to render our next frame to.
|
||||
/// We use this and `prev_offscreen_framebuffer` for double buffering when compositing to
|
||||
/// [`CompositeTarget::OffscreenFbo`].
|
||||
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::OffscreenFbo`].
|
||||
prev_offscreen_framebuffer: Option<gl::RenderTargetInfo>,
|
||||
|
||||
/// Whether to invalidate `prev_offscreen_framebuffer` at the end of the next frame.
|
||||
invalidate_prev_offscreen_framebuffer: bool,
|
||||
|
||||
/// True to exit after page load ('-x').
|
||||
exit_after_load: bool,
|
||||
|
||||
|
@ -348,10 +333,6 @@ pub enum CompositeTarget {
|
|||
/// to [`RenderingContext::framebuffer_object`]
|
||||
ContextFbo,
|
||||
|
||||
/// Draw to an offscreen OpenGL framebuffer object, which can be retrieved once complete at
|
||||
/// [`IOCompositor::offscreen_framebuffer_id`].
|
||||
OffscreenFbo,
|
||||
|
||||
/// Draw to an uncompressed image in shared memory.
|
||||
SharedMemory,
|
||||
|
||||
|
@ -399,9 +380,6 @@ impl IOCompositor {
|
|||
pending_paint_metrics: HashMap::new(),
|
||||
cursor: Cursor::None,
|
||||
cursor_pos: DevicePoint::new(0.0, 0.0),
|
||||
next_offscreen_framebuffer: OnceCell::new(),
|
||||
prev_offscreen_framebuffer: None,
|
||||
invalidate_prev_offscreen_framebuffer: false,
|
||||
exit_after_load,
|
||||
convert_mouse_to_touch,
|
||||
pending_frames: 0,
|
||||
|
@ -1302,13 +1280,6 @@ impl IOCompositor {
|
|||
let old_coords = self.embedder_coordinates;
|
||||
self.embedder_coordinates = self.window.get_coordinates();
|
||||
|
||||
// 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 {
|
||||
let mut transaction = Transaction::new();
|
||||
let size = self.embedder_coordinates.get_viewport();
|
||||
|
@ -1957,13 +1928,6 @@ impl IOCompositor {
|
|||
target: CompositeTarget,
|
||||
page_rect: Option<Rect<f32, CSSPixel>>,
|
||||
) -> Result<Option<Image>, UnableToComposite> {
|
||||
if !self.webviews_waiting_on_present.is_empty() {
|
||||
debug!("tried to composite while waiting on present");
|
||||
return Err(UnableToComposite::NotReadyToPaintImage(
|
||||
NotReadyToPaint::WaitingOnConstellation,
|
||||
));
|
||||
}
|
||||
|
||||
let size = self.embedder_coordinates.framebuffer.to_u32();
|
||||
if let Err(err) = self.rendering_context.make_current() {
|
||||
warn!("Failed to make the rendering context current: {:?}", err);
|
||||
|
@ -1978,12 +1942,6 @@ impl IOCompositor {
|
|||
target,
|
||||
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_)
|
||||
) || self.exit_after_load;
|
||||
let use_offscreen_framebuffer = matches!(
|
||||
target,
|
||||
CompositeTarget::SharedMemory |
|
||||
CompositeTarget::PngFile(_) |
|
||||
CompositeTarget::OffscreenFbo
|
||||
);
|
||||
|
||||
if wait_for_stable_image {
|
||||
// The current image may be ready to output. However, if there are animations active,
|
||||
|
@ -2000,23 +1958,7 @@ impl IOCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
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.rendering_context.framebuffer_object();
|
||||
self.webrender_gl
|
||||
.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_object);
|
||||
self.assert_gl_framebuffer_complete();
|
||||
}
|
||||
self.rendering_context.prepare_for_rendering();
|
||||
|
||||
time_profile!(
|
||||
ProfilerCategory::Compositing,
|
||||
|
@ -2056,44 +1998,20 @@ impl IOCompositor {
|
|||
|
||||
let rv = match target {
|
||||
CompositeTarget::ContextFbo => None,
|
||||
CompositeTarget::OffscreenFbo => {
|
||||
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),
|
||||
FramebufferUintLength::new(height),
|
||||
);
|
||||
Some(Image {
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
CompositeTarget::SharedMemory => self
|
||||
.rendering_context
|
||||
.read_to_image(Rect::new(
|
||||
Point2D::new(x as u32, y as u32),
|
||||
Size2D::new(width, height),
|
||||
))
|
||||
.map(|image| Image {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
format: PixelFormat::RGBA8,
|
||||
bytes: ipc::IpcSharedMemory::from_bytes(&img),
|
||||
bytes: ipc::IpcSharedMemory::from_bytes(&image),
|
||||
id: None,
|
||||
cors_status: CorsStatus::Safe,
|
||||
})
|
||||
},
|
||||
}),
|
||||
CompositeTarget::PngFile(path) => {
|
||||
time_profile!(
|
||||
ProfilerCategory::ImageSaving,
|
||||
|
@ -2101,19 +2019,15 @@ impl IOCompositor {
|
|||
self.time_profiler_chan.clone(),
|
||||
|| 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::ImageRgba8(img);
|
||||
if let Err(e) = dynamic_image.write_to(&mut file, ImageFormat::Png) {
|
||||
error!("Failed to save {} ({}).", path, e);
|
||||
if let Some(image) = self.rendering_context.read_to_image(Rect::new(
|
||||
Point2D::new(x as u32, y as u32),
|
||||
Size2D::new(width, height),
|
||||
)) {
|
||||
let dynamic_image = DynamicImage::ImageRgba8(image);
|
||||
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),
|
||||
|
@ -2205,14 +2119,6 @@ impl IOCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the OpenGL framebuffer name of the most-recently-completed frame when compositing to
|
||||
/// [`CompositeTarget::OffscreenFbo`], or None otherwise.
|
||||
pub fn offscreen_framebuffer_id(&self) -> Option<gleam::gl::GLuint> {
|
||||
self.prev_offscreen_framebuffer
|
||||
.as_ref()
|
||||
.map(|info| info.framebuffer_id())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
/* 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 std::rc::Rc;
|
||||
|
||||
use gleam::gl::{self, Gl};
|
||||
use image::RgbaImage;
|
||||
use log::{trace, warn};
|
||||
use servo_geometry::FramebufferUintLength;
|
||||
|
||||
pub struct RenderTargetInfo {
|
||||
gl: Rc<dyn Gl>,
|
||||
framebuffer_ids: Vec<gl::GLuint>,
|
||||
renderbuffer_ids: Vec<gl::GLuint>,
|
||||
texture_ids: Vec<gl::GLuint>,
|
||||
}
|
||||
|
||||
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]);
|
||||
gl.tex_image_2d(
|
||||
gl::TEXTURE_2D,
|
||||
0,
|
||||
gl::RGBA as gl::GLint,
|
||||
width.get() as gl::GLsizei,
|
||||
height.get() 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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
) -> RgbaImage {
|
||||
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::RGBA,
|
||||
gl::UNSIGNED_BYTE,
|
||||
);
|
||||
let gl_error = self.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 orig_pixels = pixels.clone();
|
||||
let stride = width * 4;
|
||||
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];
|
||||
pixels[dst_start..dst_start + stride].clone_from_slice(&src_slice[..stride]);
|
||||
}
|
||||
|
||||
RgbaImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use gleam::gl;
|
||||
use image::Rgba;
|
||||
use servo_geometry::FramebufferUintLength;
|
||||
use surfman::{Connection, ContextAttributeFlags, ContextAttributes, Error, GLApi, GLVersion};
|
||||
|
||||
use super::RenderTargetInfo;
|
||||
|
||||
#[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 WIDTH: FramebufferUintLength = FramebufferUintLength::new(16);
|
||||
const HEIGHT: FramebufferUintLength = FramebufferUintLength::new(16);
|
||||
let render_target = RenderTargetInfo::new(gl, WIDTH, HEIGHT);
|
||||
render_target.bind();
|
||||
render_target
|
||||
.gl
|
||||
.clear_color(12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0, 78.0 / 255.0);
|
||||
render_target.gl.clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
let img = render_target.read_back_from_gpu(0, 0, WIDTH, HEIGHT);
|
||||
assert_eq!(img.width(), WIDTH.get());
|
||||
assert_eq!(img.height(), HEIGHT.get());
|
||||
|
||||
let expected_pixel: Rgba<u8> = Rgba([12, 34, 56, 78]);
|
||||
assert!(img.pixels().all(|&p| p == expected_pixel));
|
||||
}
|
||||
|
||||
device.destroy_context(&mut context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ pub use crate::compositor::{CompositeTarget, IOCompositor, ShutdownState};
|
|||
mod tracing;
|
||||
|
||||
mod compositor;
|
||||
mod gl;
|
||||
mod touch;
|
||||
pub mod webview;
|
||||
pub mod windowing;
|
||||
|
|
|
@ -305,10 +305,6 @@ impl Servo {
|
|||
}
|
||||
debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR,);
|
||||
|
||||
// Bind the webrender framebuffer
|
||||
let framebuffer_object = rendering_context.framebuffer_object();
|
||||
webrender_gl.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_object);
|
||||
|
||||
// Reserving a namespace to create TopLevelBrowsingContextId.
|
||||
PipelineNamespace::install(PipelineNamespaceId(0));
|
||||
|
||||
|
@ -346,6 +342,7 @@ impl Servo {
|
|||
opts.debug.webrender_stats,
|
||||
);
|
||||
|
||||
rendering_context.prepare_for_rendering();
|
||||
let render_notifier = Box::new(RenderNotifier::new(compositor_proxy.clone()));
|
||||
let clear_color = servo_config::pref!(shell_background_color_rgba);
|
||||
let clear_color = ColorF::new(
|
||||
|
@ -354,6 +351,7 @@ impl Servo {
|
|||
clear_color[2] as f32,
|
||||
clear_color[3] as f32,
|
||||
);
|
||||
|
||||
// Use same texture upload method as Gecko with ANGLE:
|
||||
// https://searchfox.org/mozilla-central/source/gfx/webrender_bindings/src/bindings.rs#1215-1219
|
||||
let upload_method = if webrender_gl.get_string(RENDERER).starts_with("ANGLE") {
|
||||
|
@ -702,12 +700,6 @@ impl Servo {
|
|||
self.compositor.borrow_mut().present();
|
||||
}
|
||||
|
||||
/// Return the OpenGL framebuffer name of the most-recently-completed frame when compositing to
|
||||
/// [`CompositeTarget::OffscreenFbo`], or None otherwise.
|
||||
pub fn offscreen_framebuffer_id(&self) -> Option<u32> {
|
||||
self.compositor.borrow().offscreen_framebuffer_id()
|
||||
}
|
||||
|
||||
pub fn new_webview(&self, url: url::Url) -> WebView {
|
||||
let webview = WebView::new(&self.constellation_proxy, self.compositor.clone());
|
||||
self.webviews
|
||||
|
|
|
@ -10,6 +10,7 @@ rust-version.workspace = true
|
|||
[lib]
|
||||
name = "webrender_traits"
|
||||
path = "lib.rs"
|
||||
test = true
|
||||
|
||||
[features]
|
||||
no-wgl = ["surfman/sm-angle-default"]
|
||||
|
@ -18,10 +19,12 @@ no-wgl = ["surfman/sm-angle-default"]
|
|||
base = { workspace = true }
|
||||
embedder_traits = { workspace = true }
|
||||
euclid = { workspace = true }
|
||||
image = { workspace = true }
|
||||
ipc-channel = { workspace = true }
|
||||
log = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
gleam = { workspace = true }
|
||||
glow = { workspace = true }
|
||||
webrender_api = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
servo_geometry = { path = "../../geometry" }
|
||||
|
|
|
@ -4,13 +4,17 @@
|
|||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
|
||||
use euclid::default::Size2D;
|
||||
use gleam::gl;
|
||||
use log::{debug, warn};
|
||||
use euclid::default::{Rect, Size2D};
|
||||
use euclid::Point2D;
|
||||
use gleam::gl::{self, Gl};
|
||||
use glow::NativeFramebuffer;
|
||||
use image::RgbaImage;
|
||||
use log::{debug, trace, warn};
|
||||
use servo_media::player::context::{GlContext, NativeDisplay};
|
||||
use surfman::chains::{PreserveBuffer, SwapChain};
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
|
@ -36,16 +40,26 @@ pub enum GLVersion {
|
|||
/// 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: Rect<u32>) -> Option<RgbaImage>;
|
||||
/// Resizes the rendering surface to the given size.
|
||||
fn resize(&self, size: Size2D<i32>);
|
||||
/// Presents the rendered frame to the screen.
|
||||
/// 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 OpenGL framebuffer object needed to render to the surface.
|
||||
fn framebuffer_object(&self) -> u32;
|
||||
/// Returns the OpenGL or GLES API.
|
||||
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
|
||||
/// Describes the OpenGL version that is requested when a context is created.
|
||||
|
@ -86,6 +100,7 @@ pub trait RenderingContext {
|
|||
pub struct SurfmanRenderingContext(Rc<RenderingContextData>);
|
||||
|
||||
struct RenderingContextData {
|
||||
gl: Rc<dyn Gl>,
|
||||
device: RefCell<Device>,
|
||||
context: RefCell<Context>,
|
||||
// We either render to a swap buffer or to a native widget
|
||||
|
@ -164,6 +179,23 @@ impl RenderingContext for SurfmanRenderingContext {
|
|||
NativeDisplay::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_for_rendering(&self) {
|
||||
self.0.gl.bind_framebuffer(
|
||||
gleam::gl::FRAMEBUFFER,
|
||||
self.framebuffer()
|
||||
.map_or(0, |framebuffer| framebuffer.0.into()),
|
||||
);
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: Rect<u32>) -> Option<RgbaImage> {
|
||||
let framebuffer_id = self
|
||||
.framebuffer()
|
||||
.map(|framebuffer| framebuffer.0.into())
|
||||
.unwrap_or(0);
|
||||
Framebuffer::read_framebuffer_to_image(self.gl_api(), framebuffer_id, source_rectangle)
|
||||
}
|
||||
|
||||
fn resize(&self, size: Size2D<i32>) {
|
||||
if let Err(err) = self.resize(size) {
|
||||
warn!("Failed to resize surface: {:?}", err);
|
||||
|
@ -177,23 +209,9 @@ impl RenderingContext for SurfmanRenderingContext {
|
|||
fn make_current(&self) -> Result<(), Error> {
|
||||
self.make_gl_context_current()
|
||||
}
|
||||
fn framebuffer_object(&self) -> u32 {
|
||||
self.context_surface_info()
|
||||
.unwrap_or(None)
|
||||
.and_then(|info| info.framebuffer_object)
|
||||
.map(|fbo| fbo.0.get())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
#[allow(unsafe_code)]
|
||||
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
let context = self.0.context.borrow();
|
||||
let device = self.0.device.borrow();
|
||||
match self.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))
|
||||
},
|
||||
}
|
||||
self.0.gl.clone()
|
||||
}
|
||||
fn gl_version(&self) -> GLVersion {
|
||||
let device = self.0.device.borrow();
|
||||
|
@ -270,9 +288,23 @@ impl SurfmanRenderingContext {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
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))
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let device = RefCell::new(device);
|
||||
let context = RefCell::new(context);
|
||||
let data = RenderingContextData {
|
||||
gl,
|
||||
device,
|
||||
context,
|
||||
swap_chain,
|
||||
|
@ -464,4 +496,367 @@ impl SurfmanRenderingContext {
|
|||
device.make_context_current(&context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn framebuffer(&self) -> Option<NativeFramebuffer> {
|
||||
self.context_surface_info()
|
||||
.unwrap_or(None)
|
||||
.and_then(|info| info.framebuffer_object)
|
||||
}
|
||||
|
||||
/// Create a new offscreen context that is compatible with this [`SurfmanRenderingContext`].
|
||||
/// The contents of the resulting [`OffscreenRenderingContext`] are guaranteed to be blit
|
||||
/// compatible with the this context.
|
||||
pub fn offscreen_context(&self, size: Size2D<u32>) -> OffscreenRenderingContext {
|
||||
OffscreenRenderingContext::new(SurfmanRenderingContext(self.0.clone()), size)
|
||||
}
|
||||
}
|
||||
|
||||
struct Framebuffer {
|
||||
gl: Rc<dyn Gl>,
|
||||
size: Size2D<u32>,
|
||||
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: Size2D<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,
|
||||
size,
|
||||
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: Rect<u32>) -> Option<RgbaImage> {
|
||||
Self::read_framebuffer_to_image(self.gl.clone(), self.framebuffer_id, source_rectangle)
|
||||
}
|
||||
|
||||
fn read_framebuffer_to_image(
|
||||
gl: Rc<dyn Gl>,
|
||||
framebuffer_id: u32,
|
||||
source_rectangle: Rect<u32>,
|
||||
) -> 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.origin.x as i32,
|
||||
source_rectangle.origin.y as i32,
|
||||
source_rectangle.width() as gl::GLsizei,
|
||||
source_rectangle.height() as gl::GLsizei,
|
||||
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: SurfmanRenderingContext,
|
||||
size: Cell<Size2D<u32>>,
|
||||
back_framebuffer: RefCell<Framebuffer>,
|
||||
front_framebuffer: RefCell<Option<Framebuffer>>,
|
||||
}
|
||||
|
||||
type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>;
|
||||
|
||||
impl OffscreenRenderingContext {
|
||||
fn new(parent_context: SurfmanRenderingContext, size: Size2D<u32>) -> Self {
|
||||
let next_framebuffer = Framebuffer::new(parent_context.gl_api(), size);
|
||||
Self {
|
||||
parent_context,
|
||||
size: Cell::new(size),
|
||||
back_framebuffer: RefCell::new(next_framebuffer),
|
||||
front_framebuffer: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_context(&self) -> &SurfmanRenderingContext {
|
||||
&self.parent_context
|
||||
}
|
||||
|
||||
pub fn front_framebuffer_id(&self) -> Option<gl::GLuint> {
|
||||
self.front_framebuffer
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|framebuffer| framebuffer.framebuffer_id)
|
||||
}
|
||||
|
||||
pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> {
|
||||
// Don't accept a `None` context for the read framebuffer.
|
||||
let front_framebuffer_id =
|
||||
NonZeroU32::new(self.front_framebuffer_id()?).map(NativeFramebuffer)?;
|
||||
let parent_context_framebuffer_id = self.parent_context.framebuffer();
|
||||
let size = self.size.get();
|
||||
Some(Box::new(move |gl, target_rect| {
|
||||
Self::render_framebuffer_to_parent_context(
|
||||
gl,
|
||||
Rect::new(Point2D::origin(), size.to_i32()),
|
||||
front_framebuffer_id,
|
||||
target_rect,
|
||||
parent_context_framebuffer_id,
|
||||
);
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn render_framebuffer_to_parent_context(
|
||||
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 resize(&self, size: Size2D<i32>) {
|
||||
// We do not resize any buffers right now. The current buffers might be too big or too
|
||||
// small, but we only want to ensure (later) that next buffer that we draw to is the
|
||||
// correct size.
|
||||
self.size.set(size.to_u32());
|
||||
}
|
||||
|
||||
fn prepare_for_rendering(&self) {
|
||||
self.back_framebuffer.borrow().bind();
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
trace!(
|
||||
"Unbinding FBO {}",
|
||||
self.back_framebuffer.borrow().framebuffer_id
|
||||
);
|
||||
self.gl_api().bind_framebuffer(gl::FRAMEBUFFER, 0);
|
||||
|
||||
let new_back_framebuffer = match self.front_framebuffer.borrow_mut().take() {
|
||||
Some(framebuffer) if framebuffer.size == self.size.get() => framebuffer,
|
||||
_ => Framebuffer::new(self.gl_api(), self.size.get()),
|
||||
};
|
||||
|
||||
let new_front_framebuffer = std::mem::replace(
|
||||
&mut *self.back_framebuffer.borrow_mut(),
|
||||
new_back_framebuffer,
|
||||
);
|
||||
*self.front_framebuffer.borrow_mut() = Some(new_front_framebuffer);
|
||||
}
|
||||
|
||||
fn make_current(&self) -> Result<(), surfman::Error> {
|
||||
self.parent_context.make_gl_context_current()
|
||||
}
|
||||
|
||||
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
self.parent_context.gl_api()
|
||||
}
|
||||
|
||||
fn gl_version(&self) -> GLVersion {
|
||||
self.parent_context.gl_version()
|
||||
}
|
||||
|
||||
fn create_texture(&self, surface: Surface) -> Option<(SurfaceTexture, u32, Size2D<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> {
|
||||
Some(self.parent_context.connection())
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: Rect<u32>) -> Option<RgbaImage> {
|
||||
self.back_framebuffer
|
||||
.borrow()
|
||||
.read_to_image(source_rectangle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use euclid::{Point2D, Rect, 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, Size2D::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 img = framebuffer
|
||||
.read_to_image(Rect::new(Point2D::zero(), Size2D::new(SIZE, SIZE)))
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,19 +11,16 @@ use std::time::Instant;
|
|||
use std::{env, fs};
|
||||
|
||||
use log::{info, trace, warn};
|
||||
use raw_window_handle::HasDisplayHandle;
|
||||
use servo::compositing::windowing::{AnimationState, WindowMethods};
|
||||
use servo::compositing::CompositeTarget;
|
||||
use servo::config::opts::Opts;
|
||||
use servo::config::prefs::Preferences;
|
||||
use servo::servo_config::pref;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::webxr::glwindow::GlWindowDiscovery;
|
||||
#[cfg(target_os = "windows")]
|
||||
use servo::webxr::openxr::{AppInfo, OpenXrDiscovery};
|
||||
use servo::{EventLoopWaker, Servo};
|
||||
use surfman::Connection;
|
||||
use url::Url;
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
|
@ -55,18 +52,14 @@ pub struct App {
|
|||
state: AppState,
|
||||
}
|
||||
|
||||
pub(crate) enum Present {
|
||||
Deferred,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Action to be taken by the caller of [`App::handle_events`].
|
||||
pub(crate) enum PumpResult {
|
||||
/// The caller should shut down Servo and its related context.
|
||||
Shutdown,
|
||||
Continue {
|
||||
update: bool,
|
||||
present: Present,
|
||||
need_update: bool,
|
||||
new_servo_frame: bool,
|
||||
need_window_redraw: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -102,52 +95,25 @@ impl App {
|
|||
|
||||
/// Initialize Application once event loop start running.
|
||||
pub fn init(&mut self, event_loop: Option<&ActiveEventLoop>) {
|
||||
// Create rendering context
|
||||
let initial_window_size = self.servoshell_preferences.initial_window_size;
|
||||
let rendering_context = if self.servoshell_preferences.headless {
|
||||
let connection = Connection::new().expect("Failed to create connection");
|
||||
let adapter = connection
|
||||
.create_software_adapter()
|
||||
.expect("Failed to create adapter");
|
||||
SurfmanRenderingContext::create(
|
||||
&connection,
|
||||
&adapter,
|
||||
Some(initial_window_size.to_untyped().to_i32()),
|
||||
)
|
||||
.expect("Failed to create WR surfman")
|
||||
} else {
|
||||
let display_handle = event_loop
|
||||
.unwrap()
|
||||
.display_handle()
|
||||
.expect("could not get display handle from window");
|
||||
let connection = Connection::from_display_handle(display_handle)
|
||||
.expect("Failed to create connection");
|
||||
let adapter = connection
|
||||
.create_adapter()
|
||||
.expect("Failed to create adapter");
|
||||
SurfmanRenderingContext::create(&connection, &adapter, None)
|
||||
.expect("Failed to create WR surfman")
|
||||
};
|
||||
|
||||
let headless = self.servoshell_preferences.headless;
|
||||
let window = if headless {
|
||||
headless_window::Window::new(&self.servoshell_preferences)
|
||||
} else {
|
||||
Rc::new(headed_window::Window::new(
|
||||
&self.opts,
|
||||
&self.servoshell_preferences,
|
||||
&rendering_context,
|
||||
event_loop.unwrap(),
|
||||
))
|
||||
};
|
||||
|
||||
if window.winit_window().is_some() {
|
||||
self.minibrowser = Some(Minibrowser::new(
|
||||
&rendering_context,
|
||||
event_loop.unwrap(),
|
||||
self.initial_url.clone(),
|
||||
));
|
||||
}
|
||||
assert_eq!(headless, event_loop.is_none());
|
||||
let window = match event_loop {
|
||||
Some(event_loop) => {
|
||||
let window = headed_window::Window::new(
|
||||
&self.opts,
|
||||
&self.servoshell_preferences,
|
||||
event_loop,
|
||||
);
|
||||
self.minibrowser = Some(Minibrowser::new(
|
||||
window.offscreen_rendering_context(),
|
||||
event_loop,
|
||||
self.initial_url.clone(),
|
||||
));
|
||||
Rc::new(window)
|
||||
},
|
||||
None => headless_window::Window::new(&self.servoshell_preferences),
|
||||
};
|
||||
|
||||
self.windows.insert(window.id(), window);
|
||||
|
||||
|
@ -174,12 +140,6 @@ impl App {
|
|||
// Implements embedder methods, used by libservo and constellation.
|
||||
let embedder = Box::new(EmbedderCallbacks::new(self.waker.clone(), xr_discovery));
|
||||
|
||||
let composite_target = if self.minibrowser.is_some() {
|
||||
CompositeTarget::OffscreenFbo
|
||||
} else {
|
||||
CompositeTarget::ContextFbo
|
||||
};
|
||||
|
||||
// TODO: Remove this once dyn upcasting coercion stabilises
|
||||
// <https://github.com/rust-lang/rust/issues/65991>
|
||||
struct UpcastedWindow(Rc<dyn WindowPortsMethods>);
|
||||
|
@ -195,11 +155,11 @@ impl App {
|
|||
let servo = Servo::new(
|
||||
self.opts.clone(),
|
||||
self.preferences.clone(),
|
||||
Rc::new(rendering_context),
|
||||
window.rendering_context(),
|
||||
embedder,
|
||||
Rc::new(UpcastedWindow(window.clone())),
|
||||
self.servoshell_preferences.user_agent.clone(),
|
||||
composite_target,
|
||||
CompositeTarget::ContextFbo,
|
||||
);
|
||||
servo.setup_logging();
|
||||
|
||||
|
@ -233,33 +193,28 @@ impl App {
|
|||
state.shutdown();
|
||||
self.state = AppState::ShuttingDown;
|
||||
},
|
||||
PumpResult::Continue { update, present } => {
|
||||
if update {
|
||||
if let Some(ref mut minibrowser) = self.minibrowser {
|
||||
if minibrowser.update_webview_data(state) {
|
||||
// Update the minibrowser immediately. While we could update by requesting a
|
||||
// redraw, doing so would delay the location update by two frames.
|
||||
minibrowser.update(
|
||||
window.winit_window().unwrap(),
|
||||
state,
|
||||
"update_location_in_toolbar",
|
||||
);
|
||||
}
|
||||
}
|
||||
PumpResult::Continue {
|
||||
need_update: update,
|
||||
new_servo_frame,
|
||||
need_window_redraw,
|
||||
} => {
|
||||
// A new Servo frame is ready, so swap the buffer on our `RenderingContext`. In headed mode
|
||||
// this won't immediately update the widget surface, because we render to an offscreen
|
||||
// `RenderingContext`.
|
||||
if new_servo_frame {
|
||||
state.servo().present();
|
||||
}
|
||||
match present {
|
||||
Present::Deferred => {
|
||||
// The compositor has painted to this frame.
|
||||
trace!("PumpResult::Present::Deferred");
|
||||
// Request a winit redraw event, so we can paint the minibrowser and present.
|
||||
// Otherwise, it's in headless mode and we present directly.
|
||||
if let Some(window) = window.winit_window() {
|
||||
window.request_redraw();
|
||||
} else {
|
||||
state.servo().present();
|
||||
}
|
||||
},
|
||||
Present::None => {},
|
||||
|
||||
let updated = match (update, &mut self.minibrowser) {
|
||||
(true, Some(minibrowser)) => minibrowser.update_webview_data(state),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// If in headed mode, request a winit redraw event, so we can paint the minibrowser.
|
||||
if updated || need_window_redraw || new_servo_frame {
|
||||
if let Some(window) = window.winit_window() {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -292,15 +247,12 @@ impl App {
|
|||
state.shutdown();
|
||||
self.state = AppState::ShuttingDown;
|
||||
},
|
||||
PumpResult::Continue { present, .. } => {
|
||||
match present {
|
||||
Present::Deferred => {
|
||||
// The compositor has painted to this frame.
|
||||
trace!("PumpResult::Present::Deferred");
|
||||
// In headless mode, we present directly.
|
||||
state.servo().present();
|
||||
},
|
||||
Present::None => {},
|
||||
PumpResult::Continue {
|
||||
new_servo_frame, ..
|
||||
} => {
|
||||
if new_servo_frame {
|
||||
// In headless mode, we present directly.
|
||||
state.servo().present();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -401,8 +353,6 @@ impl ApplicationHandler<WakerEvent> for App {
|
|||
minibrowser.update(window.winit_window().unwrap(), state, "RedrawRequested");
|
||||
minibrowser.paint(window.winit_window().unwrap());
|
||||
}
|
||||
|
||||
state.servo().present();
|
||||
}
|
||||
|
||||
// Handle the event
|
||||
|
|
|
@ -25,7 +25,7 @@ use servo::{
|
|||
use tinyfiledialogs::MessageBoxIcon;
|
||||
use url::Url;
|
||||
|
||||
use super::app::{Present, PumpResult};
|
||||
use super::app::PumpResult;
|
||||
use super::dialog::Dialog;
|
||||
use super::gamepad::GamepadSupport;
|
||||
use super::keyutils::CMD_OR_CONTROL;
|
||||
|
@ -74,7 +74,7 @@ pub struct RunningAppStateInner {
|
|||
need_update: bool,
|
||||
|
||||
/// Whether or not the application needs to be redrawn.
|
||||
need_present: bool,
|
||||
new_servo_frame_ready: bool,
|
||||
}
|
||||
|
||||
impl Drop for RunningAppState {
|
||||
|
@ -101,7 +101,7 @@ impl RunningAppState {
|
|||
window,
|
||||
gamepad_support: GamepadSupport::maybe_new(),
|
||||
need_update: false,
|
||||
need_present: false,
|
||||
new_servo_frame_ready: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -133,28 +133,21 @@ impl RunningAppState {
|
|||
self.handle_gamepad_events();
|
||||
}
|
||||
|
||||
let should_continue = self.servo().spin_event_loop();
|
||||
|
||||
// Delegate handlers may have asked us to present or update compositor contents.
|
||||
let need_present = std::mem::replace(&mut self.inner_mut().need_present, false);
|
||||
let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
|
||||
|
||||
if !should_continue {
|
||||
if !self.servo().spin_event_loop() {
|
||||
return PumpResult::Shutdown;
|
||||
}
|
||||
|
||||
// Currently, egui-file-dialog dialogs need to be constantly presented or animations aren't fluid.
|
||||
let need_present = need_present || self.has_active_dialog();
|
||||
// Delegate handlers may have asked us to present or update compositor contents.
|
||||
let new_servo_frame = std::mem::replace(&mut self.inner_mut().new_servo_frame_ready, false);
|
||||
let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
|
||||
|
||||
let present = if need_present {
|
||||
Present::Deferred
|
||||
} else {
|
||||
Present::None
|
||||
};
|
||||
// Currently, egui-file-dialog dialogs need to be constantly redrawn or animations aren't fluid.
|
||||
let need_window_redraw = new_servo_frame || self.has_active_dialog();
|
||||
|
||||
PumpResult::Continue {
|
||||
update: need_update,
|
||||
present,
|
||||
need_update,
|
||||
new_servo_frame,
|
||||
need_window_redraw,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +233,6 @@ impl RunningAppState {
|
|||
.or_default()
|
||||
.push(dialog);
|
||||
inner_mut.need_update = true;
|
||||
inner_mut.need_present = true;
|
||||
}
|
||||
|
||||
fn has_active_dialog(&self) -> bool {
|
||||
|
@ -415,22 +407,17 @@ impl WebViewDelegate for RunningAppState {
|
|||
}
|
||||
|
||||
fn notify_ready_to_show(&self, webview: servo::WebView) {
|
||||
let scale = self.inner().window.hidpi_factor().get();
|
||||
let toolbar = self.inner().window.toolbar_height().get();
|
||||
|
||||
// Adjust for our toolbar height.
|
||||
// TODO: Adjust for egui window decorations if we end up using those
|
||||
let mut rect = self
|
||||
let rect = self
|
||||
.inner()
|
||||
.window
|
||||
.get_coordinates()
|
||||
.get_viewport()
|
||||
.to_f32();
|
||||
rect.min.y += toolbar * scale;
|
||||
|
||||
webview.focus();
|
||||
webview.move_resize(rect);
|
||||
webview.raise_to_top(true);
|
||||
webview.notify_rendering_context_resized();
|
||||
}
|
||||
|
||||
fn notify_closed(&self, webview: servo::WebView) {
|
||||
|
@ -497,7 +484,7 @@ impl WebViewDelegate for RunningAppState {
|
|||
}
|
||||
|
||||
fn notify_new_frame_ready(&self, _webview: servo::WebView) {
|
||||
self.inner_mut().need_present = true;
|
||||
self.inner_mut().new_servo_frame_ready = true;
|
||||
}
|
||||
|
||||
fn play_gamepad_haptic_effect(
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::time::Duration;
|
|||
|
||||
use euclid::{Angle, Length, Point2D, Rotation3D, Scale, Size2D, UnknownUnit, Vector2D, Vector3D};
|
||||
use keyboard_types::{Modifiers, ShortcutMatcher};
|
||||
use log::{debug, info};
|
||||
use log::{debug, info, warn};
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods,
|
||||
|
@ -22,13 +22,14 @@ use servo::servo_config::pref;
|
|||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::webrender_traits::rendering_context::{OffscreenRenderingContext, RenderingContext};
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::{
|
||||
Cursor, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton,
|
||||
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Theme, TouchEvent, TouchEventAction,
|
||||
TouchId, WebView, WheelDelta, WheelEvent, WheelMode,
|
||||
};
|
||||
use surfman::{Context, Device, SurfaceType};
|
||||
use surfman::{Connection, Context, Device, SurfaceType};
|
||||
use url::Url;
|
||||
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
use winit::event::{
|
||||
|
@ -52,9 +53,9 @@ pub struct Window {
|
|||
inner_size: Cell<PhysicalSize<u32>>,
|
||||
toolbar_height: Cell<Length<f32, DeviceIndependentPixel>>,
|
||||
mouse_down_button: Cell<Option<MouseButton>>,
|
||||
mouse_down_point: Cell<Point2D<i32, DevicePixel>>,
|
||||
webview_relative_mouse_down_point: Cell<Point2D<f32, DevicePixel>>,
|
||||
monitor: winit::monitor::MonitorHandle,
|
||||
mouse_pos: Cell<Point2D<i32, DevicePixel>>,
|
||||
webview_relative_mouse_point: Cell<Point2D<f32, DevicePixel>>,
|
||||
last_pressed: Cell<Option<(KeyboardEvent, Option<LogicalKey>)>>,
|
||||
/// A map of winit's key codes to key values that are interpreted from
|
||||
/// winit's ReceivedChar events.
|
||||
|
@ -64,13 +65,21 @@ pub struct Window {
|
|||
device_pixel_ratio_override: Option<f32>,
|
||||
xr_window_poses: RefCell<Vec<Rc<XRWindowPose>>>,
|
||||
modifiers_state: Cell<ModifiersState>,
|
||||
|
||||
/// The RenderingContext that renders directly onto the Window. This is used as
|
||||
/// the target of egui rendering and also where Servo rendering results are finally
|
||||
/// blitted.
|
||||
window_rendering_context: SurfmanRenderingContext,
|
||||
|
||||
/// The `RenderingContext` of Servo itself. This is used to render Servo results
|
||||
/// temporarily until they can be blitted into the egui scene.
|
||||
rendering_context: Rc<OffscreenRenderingContext>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(
|
||||
opts: &Opts,
|
||||
servoshell_preferences: &ServoShellPreferences,
|
||||
rendering_context: &SurfmanRenderingContext,
|
||||
event_loop: &ActiveEventLoop,
|
||||
) -> Window {
|
||||
// If there's no chrome, start off with the window invisible. It will be set to visible in
|
||||
|
@ -111,37 +120,48 @@ impl Window {
|
|||
let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
|
||||
Scale::new(screen_scale);
|
||||
let screen_size = (winit_size_to_euclid_size(screen_size).to_f64() / screen_scale).to_u32();
|
||||
let inner_size = winit_window.inner_size();
|
||||
|
||||
// Initialize surfman
|
||||
let display_handle = event_loop
|
||||
.display_handle()
|
||||
.expect("could not get display handle from window");
|
||||
let connection =
|
||||
Connection::from_display_handle(display_handle).expect("Failed to create connection");
|
||||
let adapter = connection
|
||||
.create_adapter()
|
||||
.expect("Failed to create adapter");
|
||||
let window_handle = winit_window
|
||||
.window_handle()
|
||||
.expect("could not get window handle from window");
|
||||
|
||||
let inner_size = winit_window.inner_size();
|
||||
let native_widget = rendering_context
|
||||
.connection()
|
||||
let native_widget = connection
|
||||
.create_native_widget_from_window_handle(
|
||||
window_handle,
|
||||
winit_size_to_euclid_size(inner_size).to_i32().to_untyped(),
|
||||
)
|
||||
.expect("Failed to create native widget");
|
||||
|
||||
let surface_type = SurfaceType::Widget { native_widget };
|
||||
let surface = rendering_context
|
||||
.create_surface(surface_type)
|
||||
let window_rendering_context = SurfmanRenderingContext::create(&connection, &adapter, None)
|
||||
.expect("Failed to create window RenderingContext");
|
||||
let surface = window_rendering_context
|
||||
.create_surface(SurfaceType::Widget { native_widget })
|
||||
.expect("Failed to create surface");
|
||||
rendering_context
|
||||
window_rendering_context
|
||||
.bind_surface(surface)
|
||||
.expect("Failed to bind surface");
|
||||
|
||||
// Make sure the gl context is made current.
|
||||
rendering_context.make_gl_context_current().unwrap();
|
||||
window_rendering_context.make_gl_context_current().unwrap();
|
||||
|
||||
let rendering_context_size = Size2D::new(inner_size.width, inner_size.height);
|
||||
let rendering_context =
|
||||
Rc::new(window_rendering_context.offscreen_context(rendering_context_size));
|
||||
|
||||
debug!("Created window {:?}", winit_window.id());
|
||||
Window {
|
||||
winit_window,
|
||||
mouse_down_button: Cell::new(None),
|
||||
mouse_down_point: Cell::new(Point2D::zero()),
|
||||
mouse_pos: Cell::new(Point2D::zero()),
|
||||
webview_relative_mouse_down_point: Cell::new(Point2D::zero()),
|
||||
webview_relative_mouse_point: Cell::new(Point2D::zero()),
|
||||
last_pressed: Cell::new(None),
|
||||
keys_down: RefCell::new(HashMap::new()),
|
||||
animation_state: Cell::new(AnimationState::Idle),
|
||||
|
@ -153,6 +173,8 @@ impl Window {
|
|||
xr_window_poses: RefCell::new(vec![]),
|
||||
modifiers_state: Cell::new(ModifiersState::empty()),
|
||||
toolbar_height: Cell::new(Default::default()),
|
||||
window_rendering_context,
|
||||
rendering_context,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,13 +266,7 @@ impl Window {
|
|||
}
|
||||
|
||||
/// Helper function to handle a click
|
||||
fn handle_mouse(
|
||||
&self,
|
||||
webview: &WebView,
|
||||
button: MouseButton,
|
||||
action: ElementState,
|
||||
coords: Point2D<i32, DevicePixel>,
|
||||
) {
|
||||
fn handle_mouse(&self, webview: &WebView, button: MouseButton, action: ElementState) {
|
||||
let max_pixel_dist = 10.0 * self.hidpi_factor().get();
|
||||
let mouse_button = match &button {
|
||||
MouseButton::Left => ServoMouseButton::Left,
|
||||
|
@ -261,9 +277,10 @@ impl Window {
|
|||
MouseButton::Other(value) => ServoMouseButton::Other(*value),
|
||||
};
|
||||
|
||||
let point = self.webview_relative_mouse_point.get();
|
||||
let action = match action {
|
||||
ElementState::Pressed => {
|
||||
self.mouse_down_point.set(coords);
|
||||
self.webview_relative_mouse_down_point.set(point);
|
||||
self.mouse_down_button.set(Some(button));
|
||||
MouseButtonAction::Down
|
||||
},
|
||||
|
@ -273,7 +290,7 @@ impl Window {
|
|||
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
||||
action,
|
||||
button: mouse_button,
|
||||
point: coords.to_f32(),
|
||||
point,
|
||||
}));
|
||||
|
||||
// Also send a 'click' event if this is release and the press was recorded
|
||||
|
@ -285,14 +302,13 @@ impl Window {
|
|||
}
|
||||
|
||||
if let Some(mouse_down_button) = self.mouse_down_button.get() {
|
||||
let pixel_dist = self.mouse_down_point.get() - coords;
|
||||
let pixel_dist =
|
||||
((pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y) as f32).sqrt();
|
||||
let pixel_dist = self.webview_relative_mouse_down_point.get() - point;
|
||||
let pixel_dist = (pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt();
|
||||
if mouse_down_button == button && pixel_dist < max_pixel_dist {
|
||||
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
||||
action: MouseButtonAction::Click,
|
||||
button: mouse_button,
|
||||
point: coords.to_f32(),
|
||||
point,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -418,6 +434,10 @@ impl Window {
|
|||
.otherwise(|| handled = false);
|
||||
handled
|
||||
}
|
||||
|
||||
pub(crate) fn offscreen_rendering_context(&self) -> Rc<OffscreenRenderingContext> {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowPortsMethods for Window {
|
||||
|
@ -545,15 +565,15 @@ impl WindowPortsMethods for Window {
|
|||
WindowEvent::ModifiersChanged(modifiers) => self.modifiers_state.set(modifiers.state()),
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
if button == MouseButton::Left || button == MouseButton::Right {
|
||||
self.handle_mouse(&webview, button, state, self.mouse_pos.get());
|
||||
self.handle_mouse(&webview, button, state);
|
||||
}
|
||||
},
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let position = winit_position_to_euclid_point(position);
|
||||
self.mouse_pos.set(position.to_i32());
|
||||
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent {
|
||||
point: position.to_f32(),
|
||||
}));
|
||||
let mut point = winit_position_to_euclid_point(position).to_f32();
|
||||
point.y -= (self.toolbar_height() * self.hidpi_factor()).0;
|
||||
|
||||
self.webview_relative_mouse_point.set(point);
|
||||
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
|
||||
},
|
||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||
let (mut dx, mut dy, mode) = match delta {
|
||||
|
@ -574,8 +594,8 @@ impl WindowPortsMethods for Window {
|
|||
z: 0.0,
|
||||
mode,
|
||||
};
|
||||
let pos = self.mouse_pos.get();
|
||||
let point = Point2D::new(pos.x as f32, pos.y as f32);
|
||||
let pos = self.webview_relative_mouse_point.get();
|
||||
let point = Point2D::new(pos.x, pos.y);
|
||||
|
||||
// Scroll events snap to the major axis of movement, with vertical
|
||||
// preferred over horizontal.
|
||||
|
@ -590,7 +610,11 @@ impl WindowPortsMethods for Window {
|
|||
|
||||
// Send events
|
||||
webview.notify_input_event(InputEvent::Wheel(WheelEvent { delta, point }));
|
||||
webview.notify_scroll_event(scroll_location, self.mouse_pos.get(), phase);
|
||||
webview.notify_scroll_event(
|
||||
scroll_location,
|
||||
self.webview_relative_mouse_point.get().to_i32(),
|
||||
phase,
|
||||
);
|
||||
},
|
||||
WindowEvent::Touch(touch) => {
|
||||
webview.notify_input_event(InputEvent::Touch(TouchEvent {
|
||||
|
@ -607,6 +631,14 @@ impl WindowPortsMethods for Window {
|
|||
},
|
||||
WindowEvent::Resized(new_size) => {
|
||||
if self.inner_size.get() != new_size {
|
||||
let rendering_context_size = Size2D::new(new_size.width, new_size.height);
|
||||
if let Err(error) = self
|
||||
.window_rendering_context
|
||||
.resize(rendering_context_size.to_i32())
|
||||
{
|
||||
warn!("Could not resize window RenderingContext: {error:?}");
|
||||
}
|
||||
|
||||
self.inner_size.set(new_size);
|
||||
webview.notify_rendering_context_resized();
|
||||
}
|
||||
|
@ -658,6 +690,10 @@ impl WindowPortsMethods for Window {
|
|||
fn set_toolbar_height(&self, height: Length<f32, DeviceIndependentPixel>) {
|
||||
self.toolbar_height.set(height);
|
||||
}
|
||||
|
||||
fn rendering_context(&self) -> Rc<dyn RenderingContext> {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for Window {
|
||||
|
@ -671,7 +707,9 @@ impl WindowMethods for Window {
|
|||
let window_rect = (window_rect.to_f64() / window_scale).to_i32();
|
||||
|
||||
let viewport_origin = DeviceIntPoint::zero(); // bottom left
|
||||
let viewport_size = winit_size_to_euclid_size(self.winit_window.inner_size()).to_f32();
|
||||
let mut viewport_size = winit_size_to_euclid_size(self.winit_window.inner_size()).to_f32();
|
||||
viewport_size.height -= (self.toolbar_height() * self.hidpi_factor()).0;
|
||||
|
||||
let viewport = DeviceIntRect::from_origin_and_size(viewport_origin, viewport_size.to_i32());
|
||||
let screen_size = self.screen_size.to_i32();
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ use euclid::{Box2D, Length, Point2D, Scale, Size2D};
|
|||
use servo::compositing::windowing::{AnimationState, EmbedderCoordinates, WindowMethods};
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::webrender_api::units::{DeviceIntSize, DevicePixel};
|
||||
use servo::webrender_traits::rendering_context::RenderingContext;
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use surfman::Connection;
|
||||
|
||||
use super::app_state::RunningAppState;
|
||||
use crate::desktop::window_trait::WindowPortsMethods;
|
||||
|
@ -24,19 +27,31 @@ pub struct Window {
|
|||
inner_size: Cell<DeviceIntSize>,
|
||||
screen_size: Size2D<i32, DeviceIndependentPixel>,
|
||||
window_rect: Box2D<i32, DeviceIndependentPixel>,
|
||||
rendering_context: SurfmanRenderingContext,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(servoshell_preferences: &ServoShellPreferences) -> Rc<dyn WindowPortsMethods> {
|
||||
let size = servoshell_preferences.initial_window_size;
|
||||
let connection = Connection::new().expect("Failed to create connection");
|
||||
let adapter = connection
|
||||
.create_software_adapter()
|
||||
.expect("Failed to create adapter");
|
||||
let rendering_context = SurfmanRenderingContext::create(
|
||||
&connection,
|
||||
&adapter,
|
||||
Some(size.to_untyped().to_i32()),
|
||||
)
|
||||
.expect("Failed to create WR surfman");
|
||||
|
||||
let device_pixel_ratio_override = servoshell_preferences.device_pixel_ratio_override;
|
||||
let device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>> =
|
||||
device_pixel_ratio_override.map(Scale::new);
|
||||
let hidpi_factor = device_pixel_ratio_override.unwrap_or_else(Scale::identity);
|
||||
|
||||
let size = servoshell_preferences.initial_window_size.to_i32();
|
||||
let inner_size = Cell::new((size.to_f32() * hidpi_factor).to_i32());
|
||||
let window_rect = Box2D::from_origin_and_size(Point2D::zero(), size);
|
||||
let window_rect = Box2D::from_origin_and_size(Point2D::zero(), size.to_i32());
|
||||
|
||||
let screen_size = servoshell_preferences.screen_size_override.map_or_else(
|
||||
|| window_rect.size(),
|
||||
|
@ -50,6 +65,7 @@ impl Window {
|
|||
inner_size,
|
||||
screen_size,
|
||||
window_rect,
|
||||
rendering_context,
|
||||
};
|
||||
|
||||
Rc::new(window)
|
||||
|
@ -132,6 +148,12 @@ impl WindowPortsMethods for Window {
|
|||
fn set_toolbar_height(&self, _height: Length<f32, DeviceIndependentPixel>) {
|
||||
unimplemented!("headless Window only")
|
||||
}
|
||||
|
||||
fn rendering_context(&self) -> Rc<dyn RenderingContext> {
|
||||
// `SurfmanRenderingContext` uses shared ownership internally so cloning it here does
|
||||
// not create a new one really.
|
||||
Rc::new(self.rendering_context.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for Window {
|
||||
|
|
|
@ -3,27 +3,25 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use egui::text::{CCursor, CCursorRange};
|
||||
use egui::text_edit::TextEditState;
|
||||
use egui::{
|
||||
pos2, CentralPanel, Frame, Key, Label, Modifiers, PaintCallback, Pos2, SelectableLabel,
|
||||
pos2, CentralPanel, Frame, Key, Label, Modifiers, PaintCallback, SelectableLabel,
|
||||
TopBottomPanel, Vec2,
|
||||
};
|
||||
use egui_glow::CallbackFn;
|
||||
use egui_winit::EventResponse;
|
||||
use euclid::{Box2D, Length, Point2D, Scale, Size2D};
|
||||
use gleam::gl;
|
||||
use glow::NativeFramebuffer;
|
||||
use euclid::{Box2D, Length, Point2D, Rect, Scale, Size2D};
|
||||
use log::{trace, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_api::units::DevicePixel;
|
||||
use servo::webrender_traits::SurfmanRenderingContext;
|
||||
use servo::webrender_traits::rendering_context::{OffscreenRenderingContext, RenderingContext};
|
||||
use servo::{LoadStatus, WebView};
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
|
@ -34,14 +32,11 @@ use super::egui_glue::EguiGlow;
|
|||
use super::geometry::winit_position_to_euclid_point;
|
||||
|
||||
pub struct Minibrowser {
|
||||
rendering_context: Rc<OffscreenRenderingContext>,
|
||||
pub context: EguiGlow,
|
||||
pub event_queue: RefCell<Vec<MinibrowserEvent>>,
|
||||
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>,
|
||||
|
@ -81,12 +76,14 @@ impl Drop for Minibrowser {
|
|||
|
||||
impl Minibrowser {
|
||||
pub fn new(
|
||||
rendering_context: &SurfmanRenderingContext,
|
||||
rendering_context: Rc<OffscreenRenderingContext>,
|
||||
event_loop: &ActiveEventLoop,
|
||||
initial_url: ServoUrl,
|
||||
) -> Self {
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| rendering_context.get_proc_address(s))
|
||||
glow::Context::from_loader_function(|s| {
|
||||
rendering_context.parent_context().get_proc_address(s)
|
||||
})
|
||||
};
|
||||
|
||||
// Adapted from https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs
|
||||
|
@ -99,17 +96,11 @@ impl Minibrowser {
|
|||
.egui_ctx
|
||||
.options_mut(|options| options.zoom_with_keyboard = false);
|
||||
|
||||
let widget_surface_fbo = match rendering_context.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 {
|
||||
rendering_context,
|
||||
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()),
|
||||
|
@ -268,17 +259,16 @@ impl Minibrowser {
|
|||
reason
|
||||
);
|
||||
let Self {
|
||||
rendering_context,
|
||||
context,
|
||||
event_queue,
|
||||
toolbar_height,
|
||||
widget_surface_fbo,
|
||||
last_update,
|
||||
location,
|
||||
location_dirty,
|
||||
..
|
||||
} = self;
|
||||
let widget_fbo = *widget_surface_fbo;
|
||||
let servo_framebuffer_id = state.servo().offscreen_framebuffer_id();
|
||||
|
||||
let _duration = context.run(window, |ctx| {
|
||||
// TODO: While in fullscreen add some way to mitigate the increased phishing risk
|
||||
// when not displaying the URL bar: https://github.com/servo/servo/issues/32443
|
||||
|
@ -387,26 +377,23 @@ impl Minibrowser {
|
|||
return;
|
||||
};
|
||||
CentralPanel::default().frame(Frame::NONE).show(ctx, |ui| {
|
||||
let Pos2 { x, y } = ui.cursor().min;
|
||||
let Vec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
} = ui.available_size();
|
||||
let rect =
|
||||
Box2D::from_origin_and_size(Point2D::new(x, y), Size2D::new(width, height)) *
|
||||
scale;
|
||||
// If the top parts of the GUI changed size, then update the size of the WebView and also
|
||||
// the size of its RenderingContext.
|
||||
let available_size = ui.available_size();
|
||||
let rect = Box2D::from_origin_and_size(
|
||||
Point2D::origin(),
|
||||
Size2D::new(available_size.x, available_size.y),
|
||||
) * scale;
|
||||
if rect != webview.rect() {
|
||||
webview.move_resize(rect);
|
||||
rendering_context.resize(rect.size().to_i32().to_untyped());
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
if let Some(status_text) = &self.status_text {
|
||||
egui::containers::popup::show_tooltip_at(
|
||||
ctx,
|
||||
|
@ -417,45 +404,19 @@ impl Minibrowser {
|
|||
);
|
||||
}
|
||||
|
||||
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,
|
||||
if let Some(render_to_parent) = rendering_context.render_to_parent_callback() {
|
||||
ui.painter().add(PaintCallback {
|
||||
rect,
|
||||
callback: Arc::new(CallbackFn::new(move |info, painter| {
|
||||
let clip = info.viewport_in_pixels();
|
||||
let rect_in_parent = Rect::new(
|
||||
Point2D::new(clip.left_px, clip.from_bottom_px),
|
||||
Size2D::new(clip.width_px, clip.height_px),
|
||||
);
|
||||
painter.gl().bind_framebuffer(gl::FRAMEBUFFER, widget_fbo);
|
||||
}
|
||||
})),
|
||||
});
|
||||
render_to_parent(painter.gl(), rect_in_parent)
|
||||
})),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
*last_update = now;
|
||||
|
@ -464,14 +425,11 @@ impl Minibrowser {
|
|||
|
||||
/// Paint the minibrowser, as of the last update.
|
||||
pub fn paint(&mut self, window: &Window) {
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
self.context
|
||||
.painter
|
||||
.gl()
|
||||
.bind_framebuffer(gl::FRAMEBUFFER, self.widget_surface_fbo);
|
||||
}
|
||||
self.rendering_context
|
||||
.parent_context()
|
||||
.prepare_for_rendering();
|
||||
self.context.paint(window);
|
||||
let _ = self.rendering_context.parent_context().present();
|
||||
}
|
||||
|
||||
/// Updates the location field from the given [WebViewManager], unless the user has started
|
||||
|
|
|
@ -11,6 +11,7 @@ use euclid::{Length, Scale};
|
|||
use servo::compositing::windowing::WindowMethods;
|
||||
use servo::servo_geometry::DeviceIndependentPixel;
|
||||
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
|
||||
use servo::webrender_traits::rendering_context::RenderingContext;
|
||||
use servo::{Cursor, WebView};
|
||||
|
||||
use super::app_state::RunningAppState;
|
||||
|
@ -46,4 +47,5 @@ pub trait WindowPortsMethods: WindowMethods {
|
|||
fn winit_window(&self) -> Option<&winit::window::Window>;
|
||||
fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel>;
|
||||
fn set_toolbar_height(&self, height: Length<f32, DeviceIndependentPixel>);
|
||||
fn rendering_context(&self) -> Rc<dyn RenderingContext>;
|
||||
}
|
||||
|
|
|
@ -161,7 +161,6 @@ class MachCommands(CommandBase):
|
|||
self_contained_tests = [
|
||||
"background_hang_monitor",
|
||||
"base",
|
||||
"compositing",
|
||||
"constellation",
|
||||
"fonts",
|
||||
"hyper_serde",
|
||||
|
@ -175,6 +174,7 @@ class MachCommands(CommandBase):
|
|||
"servo_config",
|
||||
"servoshell",
|
||||
"style_config",
|
||||
"webrender_traits",
|
||||
]
|
||||
if not packages:
|
||||
packages = set(os.listdir(path.join(self.context.topdir, "tests", "unit"))) - set(['.DS_Store'])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue