Simplify RenderingContext trait methods (#35251)

There are a few methods are still difficult to implement without
the help of surfman. To simplify the trait methods, all methods that
return surfman types are removed. They are either handled by
embedders themselves or abstract to simpler types that servo
components need. The most noticeable changes are:

- Methods related to native surface are moved to servo_glue. The
  embedder should decide when to remove/replace the surface and it's
  outside of servo's scope.
- Methods required by servo media now return exact media types for it.

The other major change is sevevral difficult trait methods that are
reuiqred by WebGL and Servo media have default implementation. So they
can be optional for users to implement.

Signed-off-by: Wu Wayne <yuweiwu@pm.me>
This commit is contained in:
Ngo Iok Ui (Wu Yu Wei) 2025-02-05 21:02:11 +09:00 committed by GitHub
parent 175f28866d
commit 07aa4ce093
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 176 additions and 230 deletions

1
Cargo.lock generated
View file

@ -8609,6 +8609,7 @@ dependencies = [
"libc",
"log",
"serde",
"servo-media",
"servo_geometry",
"surfman",
"webrender_api",

View file

@ -48,6 +48,10 @@ impl WebGLComm {
let webxr_init = crate::webxr::WebXRBridgeInit::new(sender.clone());
#[cfg(feature = "webxr")]
let webxr_layer_grand_manager = webxr_init.layer_grand_manager();
let connection = surfman::Connection::new().expect("Failed to create connection");
let adapter = connection
.create_adapter()
.expect("Failed to create adapter");
// This implementation creates a single `WebGLThread` for all the pipelines.
let init = WebGLThreadInit {
@ -57,8 +61,8 @@ impl WebGLComm {
sender: sender.clone(),
receiver,
webrender_swap_chains: webrender_swap_chains.clone(),
connection: rendering_context.connection(),
adapter: rendering_context.adapter(),
connection,
adapter,
api_type,
#[cfg(feature = "webxr")]
webxr_init,
@ -100,23 +104,30 @@ impl WebGLExternalImages {
debug!("... locking chain {:?}", id);
let front_buffer = self.swap_chains.get(id)?.take_surface()?;
let (surface_texture, gl_texture, size) =
self.rendering_context.create_texture(front_buffer);
if let Some((surface_texture, gl_texture, size)) =
self.rendering_context.create_texture(front_buffer)
{
self.locked_front_buffers.insert(id, surface_texture);
Some((gl_texture, size))
} else {
None
}
}
fn unlock_swap_chain(&mut self, id: WebGLContextId) -> Option<()> {
let locked_front_buffer = self.locked_front_buffers.remove(&id)?;
let locked_front_buffer = self.rendering_context.destroy_texture(locked_front_buffer);
debug!("... unlocked chain {:?}", id);
let locked_front_buffer = self.locked_front_buffers.remove(&id)?;
if let Some(locked_front_buffer) =
self.rendering_context.destroy_texture(locked_front_buffer)
{
self.swap_chains
.get(id)?
.recycle_surface(locked_front_buffer);
Some(())
} else {
None
}
}
}

View file

@ -479,23 +479,6 @@ impl IOCompositor {
self.shutdown_state = ShutdownState::FinishedShuttingDown;
}
/// The underlying native surface can be lost during servo's lifetime.
/// On Android, for example, this happens when the app is sent to background.
/// We need to unbind the surface so that we don't try to use it again.
pub fn invalidate_native_surface(&mut self) {
debug!("Invalidating native surface in compositor");
self.rendering_context.invalidate_native_surface();
}
/// On Android, this function will be called when the app moves to foreground
/// and the system creates a new native surface that needs to bound to the current
/// context.
pub fn replace_native_surface(&mut self, native_widget: *mut c_void, coords: DeviceIntSize) {
debug!("Replacing native surface in compositor: {native_widget:?}");
self.rendering_context
.replace_native_surface(native_widget, coords);
}
fn handle_browser_message(&mut self, msg: CompositorMsg) -> bool {
trace_msg_from_constellation!(msg, "{msg:?}");

View file

@ -14,7 +14,6 @@ use embedder_traits::{
};
use euclid::Scale;
use keyboard_types::{CompositionEvent, KeyboardEvent};
use libc::c_void;
use net::protocols::ProtocolRegistry;
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize, DeviceIndependentPixel};
use servo_url::ServoUrl;
@ -119,14 +118,6 @@ pub enum EmbedderEvent {
SetWebViewThrottled(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed
IMEDismissed,
/// Sent on platforms like Android where the native widget surface can be
/// automatically destroyed by the system, for example when the app
/// is sent to background.
InvalidateNativeSurface,
/// Sent on platforms like Android where system recreates a new surface for
/// the native widget when it is brough back to foreground. This event
/// carries the pointer to the native widget and its new size.
ReplaceNativeSurface(*mut c_void, DeviceIntSize),
/// Sent when new Gamepad information is available.
Gamepad(GamepadEvent),
/// Vertical Synchronization tick
@ -190,8 +181,6 @@ impl Debug for EmbedderEvent {
EmbedderEvent::SetWebViewThrottled(..) => write!(f, "SetWebViewThrottled"),
EmbedderEvent::IMEDismissed => write!(f, "IMEDismissed"),
EmbedderEvent::ClearCache => write!(f, "ClearCache"),
EmbedderEvent::InvalidateNativeSurface => write!(f, "InvalidateNativeSurface"),
EmbedderEvent::ReplaceNativeSurface(..) => write!(f, "ReplaceNativeSurface"),
EmbedderEvent::Gamepad(..) => write!(f, "Gamepad"),
EmbedderEvent::Vsync => write!(f, "Vsync"),
EmbedderEvent::ClipboardAction(_) => write!(f, "ClipboardAction"),

View file

@ -22,7 +22,7 @@ layout_2013 = ["dep:layout_thread_2013"]
media-gstreamer = ["servo-media-gstreamer", "gstreamer"]
multiview = ["compositing/multiview", "constellation/multiview"]
native-bluetooth = ["bluetooth/native-bluetooth"]
no-wgl = ["mozangle/egl", "mozangle/build_dlls", "surfman/sm-angle-default"]
no-wgl = ["mozangle/egl", "mozangle/build_dlls", "surfman/sm-angle-default", "webrender_traits/no-wgl"]
dynamic_freetype = ["webrender/dynamic_freetype"]
profilemozjs = ["script/profilemozjs"]
refcell_backtrace = ["script/refcell_backtrace"]

View file

@ -92,20 +92,13 @@ use servo_delegate::DefaultServoDelegate;
use servo_media::player::context::GlContext;
use servo_media::ServoMedia;
use servo_url::ServoUrl;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::platform::generic::multi::connection::NativeConnection as LinuxNativeConnection;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::platform::generic::multi::context::NativeContext as LinuxNativeContext;
use surfman::{GLApi, GLVersion};
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::{NativeConnection, NativeContext};
#[cfg(feature = "webgpu")]
pub use webgpu;
#[cfg(feature = "webgpu")]
use webgpu::swapchain::WGPUImageMap;
use webrender::{RenderApiSender, ShaderPrecacheFlags, UploadMethod, ONE_TIME_USAGE_HINT};
use webrender_api::{ColorF, DocumentId, FramePublishId};
use webrender_traits::rendering_context::RenderingContext;
use webrender_traits::rendering_context::{GLVersion, RenderingContext};
use webrender_traits::{
CrossProcessCompositorApi, WebrenderExternalImageHandlers, WebrenderExternalImageRegistry,
WebrenderImageHandlerType,
@ -558,58 +551,6 @@ impl Servo {
self.delegate = delegate;
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn get_native_media_display_and_gl_context(
rendering_context: &Rc<dyn RenderingContext>,
) -> Option<(NativeDisplay, GlContext)> {
let gl_context = match rendering_context.context() {
NativeContext::Default(LinuxNativeContext::Default(native_context)) => {
GlContext::Egl(native_context.egl_context as usize)
},
NativeContext::Default(LinuxNativeContext::Alternate(native_context)) => {
GlContext::Egl(native_context.egl_context as usize)
},
NativeContext::Alternate(_) => return None,
};
let native_display = match rendering_context.connection().native_connection() {
NativeConnection::Default(LinuxNativeConnection::Default(connection)) => {
NativeDisplay::Egl(connection.0 as usize)
},
NativeConnection::Default(LinuxNativeConnection::Alternate(connection)) => {
NativeDisplay::X11(connection.x11_display as usize)
},
NativeConnection::Alternate(_) => return None,
};
Some((native_display, gl_context))
}
// @TODO(victor): https://github.com/servo/media/pull/315
#[cfg(target_os = "windows")]
fn get_native_media_display_and_gl_context(
rendering_context: &Rc<dyn RenderingContext>,
) -> Option<(NativeDisplay, GlContext)> {
#[cfg(feature = "no-wgl")]
{
let gl_context = GlContext::Egl(rendering_context.context().egl_context as usize);
let native_display =
NativeDisplay::Egl(rendering_context.device().egl_display as usize);
Some((native_display, gl_context))
}
#[cfg(not(feature = "no-wgl"))]
None
}
#[cfg(not(any(
target_os = "windows",
all(target_os = "linux", not(target_env = "ohos"))
)))]
fn get_native_media_display_and_gl_context(
_rendering_context: &Rc<dyn RenderingContext>,
) -> Option<(NativeDisplay, GlContext)> {
None
}
fn create_media_window_gl_context(
external_image_handlers: &mut WebrenderExternalImageHandlers,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
@ -627,10 +568,9 @@ impl Servo {
);
}
let (native_display, gl_context) =
match Self::get_native_media_display_and_gl_context(rendering_context) {
Some((native_display, gl_context)) => (native_display, gl_context),
None => {
let native_display = rendering_context.gl_display();
let gl_context = rendering_context.gl_context();
if let (NativeDisplay::Unknown, GlContext::Unknown) = (&native_display, &gl_context) {
return (
WindowGLContext {
gl_context: GlContext::Unknown,
@ -640,15 +580,22 @@ impl Servo {
},
None,
);
}
let gl_api = match rendering_context.gl_version() {
GLVersion::GL(major, minor) => {
if major >= 3 && minor >= 2 {
GlApi::OpenGL3
} else {
GlApi::OpenGL
}
},
GLVersion::GLES(major, _) => {
if major > 1 {
GlApi::Gles2
} else {
GlApi::Gles1
}
},
};
let api = rendering_context.connection().gl_api();
let GLVersion { major, minor } = rendering_context.gl_version();
let gl_api = match api {
GLApi::GL if major >= 3 && minor >= 2 => GlApi::OpenGL3,
GLApi::GL => GlApi::OpenGL,
GLApi::GLES if major > 1 => GlApi::Gles2,
GLApi::GLES => GlApi::Gles1,
};
assert!(!matches!(gl_context, GlContext::Unknown));
@ -682,15 +629,6 @@ impl Servo {
)
}
},
EmbedderEvent::InvalidateNativeSurface => {
self.compositor.borrow_mut().invalidate_native_surface();
},
EmbedderEvent::ReplaceNativeSurface(native_widget, coords) => {
self.compositor
.borrow_mut()
.replace_native_surface(native_widget, coords);
self.compositor.borrow_mut().composite();
},
EmbedderEvent::AllowNavigationResponse(pipeline_id, allowed) => {
let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed);
if let Err(e) = self.constellation_proxy.try_send(msg) {

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
use std::hash::Hash;
use std::rc::{Rc, Weak};
use std::time::Duration;
@ -18,7 +17,7 @@ use embedder_traits::{
};
use keyboard_types::{CompositionEvent, KeyboardEvent};
use url::Url;
use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePoint, DeviceRect};
use webrender_api::units::{DeviceIntPoint, DevicePoint, DeviceRect};
use webrender_api::ScrollLocation;
use crate::webview_delegate::{DefaultWebViewDelegate, WebViewDelegate};
@ -431,24 +430,10 @@ impl WebView {
self.inner().compositor.borrow_mut().capture_webrender();
}
pub fn invalidate_native_surface(&self) {
self.inner()
.compositor
.borrow_mut()
.invalidate_native_surface();
}
pub fn composite(&self) {
self.inner().compositor.borrow_mut().composite();
}
pub fn replace_native_surface(&self, native_widget: *mut c_void, size: DeviceIntSize) {
self.inner()
.compositor
.borrow_mut()
.replace_native_surface(native_widget, size);
}
pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
self.inner()
.constellation_proxy

View file

@ -11,6 +11,9 @@ rust-version.workspace = true
name = "webrender_traits"
path = "lib.rs"
[features]
no-wgl = ["surfman/sm-angle-default"]
[dependencies]
base = { workspace = true }
embedder_traits = { workspace = true }
@ -22,5 +25,5 @@ gleam = { workspace = true }
webrender_api = { workspace = true }
serde = { workspace = true }
servo_geometry = { path = "../../geometry" }
servo-media = { workspace = true }
surfman = { workspace = true }

View file

@ -11,35 +11,35 @@ use std::rc::Rc;
use euclid::default::Size2D;
use gleam::gl;
use log::{debug, warn};
use servo_media::player::context::{GlContext, NativeDisplay};
use surfman::chains::{PreserveBuffer, SwapChain};
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::platform::generic::multi::connection::NativeConnection as LinuxNativeConnection;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use surfman::platform::generic::multi::context::NativeContext as LinuxNativeContext;
pub use surfman::Error;
use surfman::{
Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
GLVersion, NativeContext, NativeDevice, NativeWidget, Surface, SurfaceAccess, SurfaceInfo,
SurfaceTexture, SurfaceType,
NativeContext, NativeDevice, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture,
SurfaceType,
};
/// Describes the OpenGL version that is requested when a context is created.
pub enum GLVersion {
GL(u8, u8),
GLES(u8, u8),
}
/// The `RenderingContext` trait defines a set of methods for managing
/// an OpenGL or GLES rendering context.
/// Implementors of this trait are responsible for handling the creation,
/// management, and destruction of the rendering context and its associated
/// resources.
pub trait RenderingContext {
/// Returns the native OpenGL or GLES device handle
fn device(&self) -> NativeDevice;
/// Returns the native OpenGL or GLES context handle.
fn context(&self) -> NativeContext;
/// Resizes the rendering surface to the given size.
fn resize(&self, size: Size2D<i32>);
/// Presents the rendered frame to the screen.
fn present(&self);
/// Binds a native widget to the rendering context.
fn bind_native_surface_to_context(&self, native_widget: NativeWidget);
/// The connection to the display server.
fn connection(&self) -> Connection;
/// Represents a hardware display adapter that can be used for
/// rendering (including the CPU).
fn adapter(&self) -> Adapter;
/// Makes the context the current OpenGL context for this thread.
/// After calling this function, it is valid to use OpenGL rendering
/// commands.
@ -50,26 +50,23 @@ pub trait RenderingContext {
fn gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
/// Describes the OpenGL version that is requested when a context is created.
fn gl_version(&self) -> GLVersion;
/// Invalidates the native surface by unbinding it from the context.
/// This is used only on Android for when the underlying native surface
/// can be lost during servo's lifetime.
/// For example, this happens when the app is sent to background.
/// We need to unbind the surface so that we don't try to use it again.
fn invalidate_native_surface(&self);
/// Replaces the native surface with a new one.
/// This is used only on Android for when the app moves to foreground
/// and the system creates a new native surface that needs to bound to
/// the current context.
fn replace_native_surface(
&self,
native_widget: *mut c_void,
coords: euclid::Size2D<i32, webrender_api::units::DevicePixel>,
);
/// Returns the GL Context used by servo media player. Default to `GlContext::Unknown`.
fn gl_context(&self) -> GlContext {
GlContext::Unknown
}
/// Returns the GL Display used by servo media player. Default to `NativeDisplay::Unknown`.
fn gl_display(&self) -> NativeDisplay {
NativeDisplay::Unknown
}
/// Creates a texture from a given surface and returns the surface texture,
/// the OpenGL texture object, and the size of the surface.
fn create_texture(&self, surface: Surface) -> (SurfaceTexture, u32, Size2D<i32>);
/// Destroys the texture and returns the surface.
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Surface;
/// the OpenGL texture object, and the size of the surface. Default to `None`.
fn create_texture(&self, _surface: Surface) -> Option<(SurfaceTexture, u32, Size2D<i32>)> {
None
}
/// Destroys the texture and returns the surface. Default to `None`.
fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> {
None
}
}
/// A rendering context that uses the Surfman library to create and manage
@ -102,17 +99,65 @@ impl Drop for RenderingContextData {
}
impl RenderingContext for SurfmanRenderingContext {
fn device(&self) -> NativeDevice {
self.native_device()
fn gl_context(&self) -> GlContext {
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
match self.native_context() {
NativeContext::Default(LinuxNativeContext::Default(native_context)) => {
GlContext::Egl(native_context.egl_context as usize)
},
NativeContext::Default(LinuxNativeContext::Alternate(native_context)) => {
GlContext::Egl(native_context.egl_context as usize)
},
NativeContext::Alternate(_) => GlContext::Unknown,
}
fn context(&self) -> NativeContext {
self.native_context()
}
fn connection(&self) -> Connection {
self.connection()
#[cfg(target_os = "windows")]
{
#[cfg(feature = "no-wgl")]
{
GlContext::Egl(self.native_context().egl_context as usize)
}
#[cfg(not(feature = "no-wgl"))]
GlContext::Unknown
}
#[cfg(not(any(
target_os = "windows",
all(target_os = "linux", not(target_env = "ohos"))
)))]
{
GlContext::Unknown
}
}
fn gl_display(&self) -> NativeDisplay {
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
match self.connection().native_connection() {
surfman::NativeConnection::Default(LinuxNativeConnection::Default(connection)) => {
NativeDisplay::Egl(connection.0 as usize)
},
surfman::NativeConnection::Default(LinuxNativeConnection::Alternate(
connection,
)) => NativeDisplay::X11(connection.x11_display as usize),
surfman::NativeConnection::Alternate(_) => NativeDisplay::Unknown,
}
}
#[cfg(target_os = "windows")]
{
#[cfg(feature = "no-wgl")]
{
NativeDisplay::Egl(self.native_device().egl_display as usize)
}
#[cfg(not(feature = "no-wgl"))]
NativeDisplay::Unknown
}
#[cfg(not(any(
target_os = "windows",
all(target_os = "linux", not(target_env = "ohos"))
)))]
{
NativeDisplay::Unknown
}
fn adapter(&self) -> Adapter {
self.adapter()
}
fn resize(&self, size: Size2D<i32>) {
if let Err(err) = self.resize(size) {
@ -124,11 +169,6 @@ impl RenderingContext for SurfmanRenderingContext {
warn!("Failed to present surface: {:?}", err);
}
}
fn bind_native_surface_to_context(&self, native_widget: NativeWidget) {
if let Err(err) = self.bind_native_surface_to_context(native_widget) {
warn!("Failed to bind native surface to context: {:?}", err);
}
}
fn make_current(&self) -> Result<(), Error> {
self.make_gl_context_current()
}
@ -155,29 +195,15 @@ impl RenderingContext for SurfmanRenderingContext {
let context = self.0.context.borrow();
let descriptor = device.context_descriptor(&context);
let attributes = device.context_descriptor_attributes(&descriptor);
attributes.version
}
fn invalidate_native_surface(&self) {
if let Err(e) = self.unbind_native_surface_from_context() {
warn!("Unbinding native surface from context failed ({:?})", e);
}
}
#[allow(unsafe_code)]
#[allow(clippy::not_unsafe_ptr_arg_deref)] // It has an unsafe block inside
fn replace_native_surface(
&self,
native_widget: *mut c_void,
coords: euclid::Size2D<i32, webrender_api::units::DevicePixel>,
) {
let connection = self.connection();
let native_widget =
unsafe { connection.create_native_widget_from_ptr(native_widget, coords.to_untyped()) };
if let Err(e) = self.bind_native_surface_to_context(native_widget) {
warn!("Binding native surface to context failed ({:?})", e);
let major = attributes.version.major;
let minor = attributes.version.minor;
match self.connection().gl_api() {
GLApi::GL => GLVersion::GL(major, minor),
GLApi::GLES => GLVersion::GLES(major, minor),
}
}
fn create_texture(&self, surface: Surface) -> (SurfaceTexture, u32, Size2D<i32>) {
fn create_texture(&self, surface: Surface) -> Option<(SurfaceTexture, u32, Size2D<i32>)> {
let device = &self.0.device.borrow();
let context = &mut self.0.context.borrow_mut();
let SurfaceInfo {
@ -187,16 +213,15 @@ impl RenderingContext for SurfmanRenderingContext {
} = device.surface_info(&surface);
debug!("... getting texture for surface {:?}", front_buffer_id);
let surface_texture = device.create_surface_texture(context, surface).unwrap();
let gl_texture = device.surface_texture_object(&surface_texture);
(
surface_texture,
gl_texture.map(|tex| tex.0.get()).unwrap_or(0),
size,
)
let gl_texture = device
.surface_texture_object(&surface_texture)
.map(|tex| tex.0.get())
.unwrap_or(0);
Some((surface_texture, gl_texture, size))
}
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Surface {
self.destroy_surface_texture(surface_texture).unwrap()
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
self.destroy_surface_texture(surface_texture).ok()
}
}
@ -211,8 +236,8 @@ impl SurfmanRenderingContext {
ContextAttributeFlags::DEPTH |
ContextAttributeFlags::STENCIL;
let version = match connection.gl_api() {
GLApi::GLES => GLVersion { major: 3, minor: 0 },
GLApi::GL => GLVersion { major: 3, minor: 2 },
GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 },
GLApi::GL => surfman::GLVersion { major: 3, minor: 2 },
};
let context_attributes = ContextAttributes { flags, version };
let context_descriptor = device.create_context_descriptor(&context_attributes)?;

View file

@ -405,7 +405,9 @@ impl ServoGlue {
}
pub fn pause_compositor(&mut self) {
self.active_webview().invalidate_native_surface();
if let Err(e) = self.rendering_context.unbind_native_surface_from_context() {
warn!("Unbinding native surface from context failed ({:?})", e);
}
self.maybe_perform_updates();
}
@ -413,8 +415,17 @@ impl ServoGlue {
if native_surface.is_null() {
panic!("null passed for native_surface");
}
self.active_webview()
.replace_native_surface(native_surface, coords.framebuffer);
let connection = self.rendering_context.connection();
let native_widget = unsafe {
connection
.create_native_widget_from_ptr(native_surface, coords.framebuffer.to_untyped())
};
if let Err(e) = self
.rendering_context
.bind_native_surface_to_context(native_widget)
{
warn!("Binding native surface to context failed ({:?})", e);
}
self.maybe_perform_updates()
}