libservo: Expose SoftwareRenderingContext and WindowRenderingContext (#35501)

Expose two easy-to-use wrappers around `SurfmanRenderingContext` that
make the API simpler to use:

- `WindowRenderingContext`: This `RenderingContext` is a newtype around
  `SurfmanRenderingContext` takes a `raw-window-handle` display and window
  and creates a full window rendering context.
- `SoftwareRenderingContext`: is wraps `SurfmanRenderingContext` and
  adds a swap chain in order to expose a software GL rendering context.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-02-18 15:50:41 +01:00 committed by GitHub
parent 73507f58e6
commit f34f2d9d0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 514 additions and 470 deletions

View file

@ -57,6 +57,7 @@ webxr = ["libservo/webxr"]
webgpu = ["libservo/webgpu"]
[dependencies]
dpi = { workspace = true }
euclid = { workspace = true }
libc = { workspace = true }
libservo = { path = "../../components/servo", features = ["background_hang_monitor", "bluetooth"] }
@ -67,6 +68,7 @@ getopts = { workspace = true }
hitrace = { workspace = true, optional = true }
mime_guess = { workspace = true }
url = { workspace = true }
raw-window-handle = { workspace = true }
rustls = { workspace = true, features = ["aws-lc-rs"] }
tokio = { workspace = true }
tracing = { workspace = true, optional = true }
@ -116,7 +118,6 @@ headers = { workspace = true }
http = { workspace = true }
net = { path = "../../components/net" }
net_traits = { workspace = true }
raw-window-handle = "0.6"
serde_json = { workspace = true }
shellwords = "1.0.0"
surfman = { workspace = true, features = ["sm-x11", "sm-raw-window-handle-06"] }

View file

@ -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, warn};
use log::{debug, info};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods,
@ -22,14 +22,13 @@ 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, TouchAction, TouchEvent,
TouchEventType, TouchId, WebView, WheelDelta, WheelEvent, WheelMode,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext,
RenderingContext, Theme, TouchAction, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta,
WheelEvent, WheelMode, WindowRenderingContext,
};
use surfman::{Connection, Context, Device, SurfaceType};
use surfman::{Context, Device};
use url::Url;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{
@ -69,7 +68,7 @@ pub struct Window {
/// 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,
window_rendering_context: Rc<WindowRenderingContext>,
/// The `RenderingContext` of Servo itself. This is used to render Servo results
/// temporarily until they can be blitted into the egui scene.
@ -125,32 +124,16 @@ impl Window {
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 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 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");
window_rendering_context
.bind_surface(surface)
.expect("Failed to bind surface");
let window_rendering_context = Rc::new(
WindowRenderingContext::new(display_handle, window_handle, &inner_size)
.expect("Could not create RenderingContext for Window"),
);
// Make sure the gl context is made current.
window_rendering_context.make_gl_context_current().unwrap();
window_rendering_context.make_current().unwrap();
let rendering_context_size = Size2D::new(inner_size.width, inner_size.height);
let rendering_context =
@ -633,13 +616,8 @@ 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.window_rendering_context
.resize(rendering_context_size.to_i32());
self.inner_size.set(new_size);
webview.notify_rendering_context_resized();
}

View file

@ -12,9 +12,8 @@ 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 servo::{RenderingContext, SoftwareRenderingContext};
use winit::dpi::PhysicalSize;
use super::app_state::RunningAppState;
use crate::desktop::window_trait::WindowPortsMethods;
@ -27,30 +26,24 @@ pub struct Window {
inner_size: Cell<DeviceIntSize>,
screen_size: Size2D<i32, DeviceIndependentPixel>,
window_rect: Box2D<i32, DeviceIndependentPixel>,
rendering_context: SurfmanRenderingContext,
rendering_context: Rc<SoftwareRenderingContext>,
}
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 inner_size = Cell::new((size.to_f32() * hidpi_factor).to_i32());
let inner_size = (size.to_f32() * hidpi_factor).to_i32();
let physical_size = PhysicalSize::new(inner_size.width as u32, inner_size.height as u32);
let rendering_context =
SoftwareRenderingContext::new(physical_size).expect("Failed to create WR surfman");
let window_rect = Box2D::from_origin_and_size(Point2D::zero(), size.to_i32());
let screen_size = servoshell_preferences.screen_size_override.map_or_else(
@ -62,10 +55,10 @@ impl Window {
animation_state: Cell::new(AnimationState::Idle),
fullscreen: Cell::new(false),
device_pixel_ratio_override,
inner_size,
inner_size: Cell::new(inner_size),
screen_size,
window_rect,
rendering_context,
rendering_context: Rc::new(rendering_context),
};
Rc::new(window)
@ -150,9 +143,7 @@ impl WindowPortsMethods for Window {
}
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())
self.rendering_context.clone()
}
}

View file

@ -21,8 +21,7 @@ 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::rendering_context::{OffscreenRenderingContext, RenderingContext};
use servo::{LoadStatus, WebView};
use servo::{LoadStatus, OffscreenRenderingContext, RenderingContext, WebView};
use winit::event::{ElementState, MouseButton, WindowEvent};
use winit::event_loop::ActiveEventLoop;
use winit::window::Window;
@ -429,7 +428,7 @@ impl Minibrowser {
.parent_context()
.prepare_for_rendering();
self.context.paint(window);
let _ = self.rendering_context.parent_context().present();
self.rendering_context.parent_context().present();
}
/// Updates the location field from the given [WebViewManager], unless the user has started

View file

@ -11,8 +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 servo::{Cursor, RenderingContext, WebView};
use super::app_state::RunningAppState;

View file

@ -8,6 +8,7 @@ mod resources;
mod simpleservo;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::NonNull;
use std::sync::Arc;
use android_logger::{self, Config, FilterBuilder};
@ -15,6 +16,9 @@ use jni::objects::{GlobalRef, JClass, JObject, JString, JValue, JValueOwned};
use jni::sys::{jboolean, jfloat, jint, jobject};
use jni::{JNIEnv, JavaVM};
use log::{debug, error, info, warn};
use raw_window_handle::{
AndroidDisplayHandle, AndroidNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use servo::{LoadStatus, MediaSessionActionType};
use simpleservo::{
DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState,
@ -391,12 +395,15 @@ pub extern "C" fn Java_org_servo_servoview_JNIServo_resumeCompositor<'local>(
coordinates: JObject<'local>,
) {
debug!("resumeCompositor");
let widget = unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface.as_raw()) };
let coords = jni_coords_to_rust_coords(&mut env, &coordinates);
match coords {
Ok(coords) => call(&mut env, |s| s.resume_compositor(widget, coords.clone())),
Err(error) => throw(&mut env, &error),
}
let coords = match jni_coords_to_rust_coords(&mut env, &coordinates) {
Ok(coords) => coords,
Err(error) => return throw(&mut env, &error),
};
let (_, window_handle) = display_and_window_handle(&mut env, &surface);
call(&mut env, |s| {
s.resume_compositor(window_handle, coords.clone())
});
}
#[no_mangle]
@ -795,17 +802,29 @@ fn get_options<'local>(
None => None,
};
let native_window =
unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface.as_raw()) };
let (display_handle, window_handle) = display_and_window_handle(env, surface);
let opts = InitOptions {
args: args.unwrap_or(vec![]),
url,
coordinates,
density,
xr_discovery: None,
surfman_integration: simpleservo::SurfmanIntegration::Widget(native_window),
window_handle,
display_handle,
};
Ok((opts, log, log_str, gst_debug_str))
}
fn display_and_window_handle(
env: &mut JNIEnv<'_>,
surface: &JObject<'_>,
) -> (RawDisplayHandle, RawWindowHandle) {
let native_window =
unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface.as_raw()) };
let native_window = NonNull::new(native_window).expect("Could not get Android window");
(
RawDisplayHandle::Android(AndroidDisplayHandle::new()),
RawWindowHandle::AndroidNdk(AndroidNdkWindowHandle::new(native_window)),
)
}

View file

@ -4,19 +4,17 @@
use std::cell::RefCell;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
use raw_window_handle::{DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle};
use servo::compositing::CompositeTarget;
pub use servo::webrender_api::units::DeviceIntRect;
use servo::webrender_traits::SurfmanRenderingContext;
/// The EventLoopWaker::wake function will be called from any thread.
/// It will be called to notify embedder that some events are available,
/// and that perform_updates need to be called
pub use servo::EventLoopWaker;
use servo::{self, resources, Servo};
pub use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult};
use surfman::{Connection, SurfaceType};
pub use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult, WindowRenderingContext};
use crate::egl::android::resources::ResourceReaderInstance;
use crate::egl::app_state::{
@ -36,13 +34,8 @@ pub struct InitOptions {
pub density: f32,
#[cfg(feature = "webxr")]
pub xr_discovery: Option<servo::webxr::Discovery>,
pub surfman_integration: SurfmanIntegration,
}
/// Controls how this embedding's rendering will integrate with the embedder.
pub enum SurfmanIntegration {
/// Render directly to a provided native widget (see surfman::NativeWidget).
Widget(*mut c_void),
pub window_handle: RawWindowHandle,
pub display_handle: RawDisplayHandle,
}
/// Initialize Servo. At that point, we need a valid GL context.
@ -70,30 +63,20 @@ pub fn init(
crate::init_tracing(servoshell_preferences.tracing_filter.as_deref());
// Initialize surfman
let connection = Connection::new().or(Err("Failed to create connection"))?;
let adapter = connection
.create_adapter()
.or(Err("Failed to create adapter"))?;
let surface_type = match init_opts.surfman_integration {
SurfmanIntegration::Widget(native_widget) => {
let native_widget = unsafe {
connection.create_native_widget_from_ptr(
native_widget,
init_opts.coordinates.framebuffer.to_untyped(),
)
};
SurfaceType::Widget { native_widget }
},
let (display_handle, window_handle) = unsafe {
(
DisplayHandle::borrow_raw(init_opts.display_handle),
WindowHandle::borrow_raw(init_opts.window_handle),
)
};
let rendering_context = SurfmanRenderingContext::create(&connection, &adapter, None)
.or(Err("Failed to create surface manager"))?;
let surface = rendering_context
.create_surface(surface_type)
.or(Err("Failed to create surface"))?;
rendering_context
.bind_surface(surface)
.or(Err("Failed to bind surface"))?;
let rendering_context = Rc::new(
WindowRenderingContext::new(
display_handle,
window_handle,
&init_opts.coordinates.framebuffer_size(),
)
.expect("Could not create RenderingContext"),
);
let window_callbacks = Rc::new(ServoWindowCallbacks::new(
callbacks,
@ -110,7 +93,7 @@ pub fn init(
let servo = Servo::new(
opts,
preferences,
Rc::new(rendering_context.clone()),
rendering_context.clone(),
embedder_callbacks,
window_callbacks.clone(),
None,

View file

@ -3,12 +3,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::os::raw::c_void;
use std::rc::Rc;
use dpi::PhysicalSize;
use ipc_channel::ipc::IpcSender;
use keyboard_types::{CompositionEvent, CompositionState};
use log::{debug, error, info, warn};
use raw_window_handle::{RawWindowHandle, WindowHandle};
use servo::base::id::WebViewId;
use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderMethods, WindowMethods,
@ -17,14 +18,13 @@ use servo::euclid::{Box2D, Point2D, Rect, Scale, Size2D, Vector2D};
use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntRect, DeviceIntSize, DevicePixel, DeviceRect};
use servo::webrender_api::ScrollLocation;
use servo::webrender_traits::SurfmanRenderingContext;
use servo::{
AllowOrDenyRequest, ContextMenuResult, EmbedderProxy, EventLoopWaker, ImeEvent, InputEvent,
InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType,
MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, Servo,
ServoDelegate, ServoError, TouchAction, TouchEvent, TouchEventType, TouchId, WebView,
WebViewDelegate,
NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
RenderingContext, Servo, ServoDelegate, ServoError, TouchAction, TouchEvent, TouchEventType,
TouchId, WebView, WebViewDelegate, WindowRenderingContext,
};
use url::Url;
@ -51,6 +51,13 @@ impl Coordinates {
framebuffer: Size2D::new(fb_width, fb_height),
}
}
pub(crate) fn framebuffer_size(&self) -> PhysicalSize<u32> {
PhysicalSize::new(
self.framebuffer.width as u32,
self.framebuffer.height as u32,
)
}
}
pub(super) struct ServoWindowCallbacks {
@ -75,7 +82,7 @@ impl ServoWindowCallbacks {
pub struct RunningAppState {
servo: Servo,
rendering_context: SurfmanRenderingContext,
rendering_context: Rc<WindowRenderingContext>,
callbacks: Rc<ServoWindowCallbacks>,
inner: RefCell<RunningAppStateInner>,
/// servoshell specific preferences created during startup of the application.
@ -283,7 +290,7 @@ impl WebViewDelegate for RunningAppState {
impl RunningAppState {
pub(super) fn new(
initial_url: Option<String>,
rendering_context: SurfmanRenderingContext,
rendering_context: Rc<WindowRenderingContext>,
servo: Servo,
callbacks: Rc<ServoWindowCallbacks>,
servoshell_preferences: ServoShellPreferences,
@ -367,12 +374,6 @@ impl RunningAppState {
self.servo.deinit();
}
/// Returns the webrender surface management integration interface.
/// This provides the embedder access to the current front buffer.
pub fn surfman(&self) -> SurfmanRenderingContext {
self.rendering_context.clone()
}
/// This is the Servo heartbeat. This needs to be called
/// everytime wakeup is called or when embedder wants Servo
/// to act on its pending events.
@ -429,10 +430,8 @@ impl RunningAppState {
pub fn resize(&self, coordinates: Coordinates) {
info!("resize to {:?}", coordinates);
let size = coordinates.viewport.size;
let _ = self
.rendering_context
.resize(Size2D::new(size.width, size.height))
.inspect_err(|e| error!("Failed to resize rendering context: {e:?}"));
self.rendering_context
.resize(Size2D::new(size.width, size.height));
*self.callbacks.coordinates.borrow_mut() = coordinates;
self.active_webview().notify_rendering_context_resized();
self.active_webview()
@ -632,24 +631,17 @@ impl RunningAppState {
}
pub fn pause_compositor(&self) {
if let Err(e) = self.rendering_context.unbind_native_surface_from_context() {
if let Err(e) = self.rendering_context.take_window() {
warn!("Unbinding native surface from context failed ({:?})", e);
}
self.perform_updates();
}
pub fn resume_compositor(&self, native_surface: *mut c_void, coords: Coordinates) {
if native_surface.is_null() {
panic!("null passed for native_surface");
}
let connection = self.rendering_context.connection();
let native_widget = unsafe {
connection
.create_native_widget_from_ptr(native_surface, coords.framebuffer.to_untyped())
};
pub fn resume_compositor(&self, window_handle: RawWindowHandle, coords: Coordinates) {
let window_handle = unsafe { WindowHandle::borrow_raw(window_handle) };
if let Err(e) = self
.rendering_context
.bind_native_surface_to_context(native_widget)
.set_window(window_handle, &coords.framebuffer_size())
{
warn!("Binding native surface to context failed ({:?})", e);
}
@ -688,6 +680,7 @@ impl RunningAppState {
pub fn present_if_needed(&self) {
if self.inner().need_present {
self.inner_mut().need_present = false;
self.active_webview().paint_immediately();
self.servo.present();
}
}

View file

@ -2,20 +2,22 @@
* 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::RefCell;
use std::convert::TryInto;
use std::os::raw::c_void;
use std::path::PathBuf;
use std::ptr::NonNull;
use std::rc::Rc;
use log::{debug, info};
use raw_window_handle::{
DisplayHandle, OhosDisplayHandle, OhosNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
WindowHandle,
};
use servo::compositing::CompositeTarget;
use servo::webrender_traits::SurfmanRenderingContext;
/// The EventLoopWaker::wake function will be called from any thread.
/// It will be called to notify embedder that some events are available,
/// and that perform_updates need to be called
pub use servo::EventLoopWaker;
use servo::{self, resources, Servo};
use surfman::{Connection, SurfaceType};
use servo::{self, resources, Servo, WindowRenderingContext};
use xcomponent_sys::OH_NativeXComponent;
use crate::egl::app_state::{
@ -62,43 +64,39 @@ pub fn init(
crate::init_tracing(servoshell_preferences.tracing_filter.as_deref());
// Initialize surfman
let connection = Connection::new().or(Err("Failed to create connection"))?;
let adapter = connection
.create_adapter()
.or(Err("Failed to create adapter"))?;
let Ok(window_size) = (unsafe { super::get_xcomponent_size(xcomponent, native_window) }) else {
return Err("Failed to get xcomponent size");
};
let coordinates = Coordinates::new(
0,
0,
window_size.width,
window_size.height,
window_size.width,
window_size.height,
);
debug!("Creating surfman widget with {window_size:?}");
let native_widget =
unsafe { connection.create_native_widget_from_ptr(native_window, window_size) };
let surface_type = SurfaceType::Widget { native_widget };
let display_handle = RawDisplayHandle::Ohos(OhosDisplayHandle::new());
let display_handle = unsafe { DisplayHandle::borrow_raw(display_handle) };
info!("Creating rendering context");
let rendering_context = SurfmanRenderingContext::create(&connection, &adapter, None)
.or(Err("Failed to create surface manager"))?;
let surface = rendering_context
.create_surface(surface_type)
.or(Err("Failed to create surface"))?;
rendering_context
.bind_surface(surface)
.or(Err("Failed to bind surface"))?;
let native_window = NonNull::new(native_window).expect("Could not get native window");
let window_handle = RawWindowHandle::OhosNdk(OhosNdkWindowHandle::new(native_window));
let window_handle = unsafe { WindowHandle::borrow_raw(window_handle) };
let rendering_context = Rc::new(
WindowRenderingContext::new(
display_handle,
window_handle,
&coordinates.framebuffer_size(),
)
.expect("Could not create RenderingContext"),
);
info!("before ServoWindowCallbacks...");
let window_callbacks = Rc::new(ServoWindowCallbacks::new(
callbacks,
RefCell::new(Coordinates::new(
0,
0,
window_size.width,
window_size.height,
window_size.width,
window_size.height,
)),
RefCell::new(coordinates),
options.display_density as f32,
));
@ -111,7 +109,7 @@ pub fn init(
let servo = Servo::new(
opts,
preferences,
Rc::new(rendering_context.clone()),
rendering_context.clone(),
embedder_callbacks,
window_callbacks.clone(),
None, /* user_agent */