From a7d3e896bb81b13d07b81b57db85e7b1b32accd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 10 May 2026 20:55:35 -0400 Subject: [PATCH] Wire the vendored hardware context into SoftwareServoHost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `HardwareOffscreenContext` was vendored in 048c5df but the host's per-webview `new_rendering_context` still hard-wired `servo::SoftwareRenderingContext`. This commit threads a `RenderingContextKind` enum through the host so existing call sites keep their software path, and new callers can opt into the hardware path through `SoftwareServoHost::new_with_config_dir_and_kind(...)`. Three changes, kept tightly scoped: * `runtime.rs` gains a public `RenderingContextKind { Software, Hardware }` enum and a new constructor that takes it. The existing `new` and `new_with_config_dir` keep their signatures and default to `Software`, so the sidecar binary and the integration tests pick up zero behavioural change. The private `new_rendering_context` moves from a free function to a `&self` method so it can read `self.rendering_context_kind` and dispatch — `Software` constructs `SoftwareRenderingContext` as before, `Hardware` constructs the vendored `HardwareOffscreenContext`. When the `hardware-render` feature isn't compiled in, the `Hardware` arm returns `ServoHostError::HardwareRenderUnavailable` instead of silently falling back; the new constructor also rejects the request up-front before touching the global Servo runtime flag. * `error.rs` gains `HardwareRenderUnavailable` so the wrong-feature path is a typed error, not a panic. * `lib.rs` exports `RenderingContextKind` alongside `SoftwareServoHost` so downstream code (next commit will be the sidecar's `--rendering-context` CLI flag and the live.rs plumbing) can name the variant directly. This is purely an extension point — no existing call path changes, no existing test asserts on the new enum. The next commit will add the sidecar CLI flag and wire `live.rs::run_live` to pass the kind through to the host so users can pick the path at startup. The follow-up commits then extract the IOSurface from the hardware surfman surface and bridge it across the IPC channel to GPUI's Metal renderer, deleting the host-side `Vec` from the per-frame hot path entirely (full plan in docs/t10-iosurface-plan.md). cargo test -p ely_servo_host --features servo-engine --lib: 2 passed. cargo test -p ely_servo_host --features servo-engine --test sidecar: 9 passed. cargo test -p ely_servo_host --features servo-engine,hardware-render --test hardware_rendering_context: 1 passed. cargo test --bin ely_app: 120 passed, 0 failed, 2 ignored. --- crates/ely_servo_host/src/error.rs | 6 +++ crates/ely_servo_host/src/lib.rs | 2 +- crates/ely_servo_host/src/runtime.rs | 68 +++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/crates/ely_servo_host/src/error.rs b/crates/ely_servo_host/src/error.rs index c034886..e1b9767 100644 --- a/crates/ely_servo_host/src/error.rs +++ b/crates/ely_servo_host/src/error.rs @@ -24,6 +24,12 @@ pub enum ServoHostError { #[error("servo rendering context could not be made current")] RenderingContextNotCurrent, + #[error( + "hardware rendering context requested but the `hardware-render` feature \ + was not compiled in; rebuild with --features servo-engine,hardware-render" + )] + HardwareRenderUnavailable, + #[error("servo rendered frame is unavailable")] RenderedFrameUnavailable, diff --git a/crates/ely_servo_host/src/lib.rs b/crates/ely_servo_host/src/lib.rs index 8317fec..ce9cbaa 100644 --- a/crates/ely_servo_host/src/lib.rs +++ b/crates/ely_servo_host/src/lib.rs @@ -25,4 +25,4 @@ pub use host::{ TouchTapRequest, WebViewSnapshot, WebViewState, }; #[cfg(feature = "servo-engine")] -pub use runtime::{ServoSurfaceSize, SoftwareServoHost}; +pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost}; diff --git a/crates/ely_servo_host/src/runtime.rs b/crates/ely_servo_host/src/runtime.rs index e8e5050..b047cc2 100644 --- a/crates/ely_servo_host/src/runtime.rs +++ b/crates/ely_servo_host/src/runtime.rs @@ -53,9 +53,27 @@ impl ServoSurfaceSize { } } +/// Selects the `RenderingContext` implementation each webview gets. +/// +/// `Software` uses Servo's built-in `SoftwareRenderingContext`, which +/// rasterises on the CPU. `Hardware` uses the vendored +/// [`HardwareOffscreenContext`](crate::HardwareOffscreenContext), +/// which rasterises through the real GPU adapter against a +/// `SurfaceType::Generic` offscreen surface. The `Hardware` variant +/// is only available when the `hardware-render` feature is enabled; +/// requesting it without the feature is a configuration error +/// surfaced via `ServoHostError::HardwareRenderUnavailable`. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum RenderingContextKind { + #[default] + Software, + Hardware, +} + pub struct SoftwareServoHost { servo: Servo, default_surface_size: ServoSurfaceSize, + rendering_context_kind: RenderingContextKind, webviews: HashMap, permissions: PermissionStore, wake_requested: Arc, @@ -64,13 +82,35 @@ pub struct SoftwareServoHost { impl SoftwareServoHost { pub fn new(size: ServoSurfaceSize) -> Result { - Self::new_with_config_dir(size, None) + Self::new_with_config_dir_and_kind(size, None, RenderingContextKind::Software) } pub fn new_with_config_dir( size: ServoSurfaceSize, config_dir: Option, ) -> Result { + Self::new_with_config_dir_and_kind(size, config_dir, RenderingContextKind::Software) + } + + /// Construct the host with an explicit [`RenderingContextKind`]. + /// + /// `Hardware` requires the `hardware-render` feature; the call + /// fails with `ServoHostError::HardwareRenderUnavailable` if the + /// feature wasn't compiled in. This is the constructor the + /// sidecar binary will use once a `--rendering-context` CLI + /// flag lands; today the default path through `new` and + /// `new_with_config_dir` keeps the software behaviour unchanged. + pub fn new_with_config_dir_and_kind( + size: ServoSurfaceSize, + config_dir: Option, + rendering_context_kind: RenderingContextKind, + ) -> Result { + if rendering_context_kind == RenderingContextKind::Hardware + && !cfg!(feature = "hardware-render") + { + return Err(ServoHostError::HardwareRenderUnavailable); + } + if SERVO_RUNTIME_STARTED .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .is_err() @@ -78,7 +118,7 @@ impl SoftwareServoHost { return Err(ServoHostError::RuntimeAlreadyStarted); } - let host = Self::new_started(size, config_dir); + let host = Self::new_started(size, config_dir, rendering_context_kind); if host.is_err() { SERVO_RUNTIME_STARTED.store(false, Ordering::Release); } @@ -97,6 +137,7 @@ impl SoftwareServoHost { fn new_started( size: ServoSurfaceSize, config_dir: Option, + rendering_context_kind: RenderingContextKind, ) -> Result { let wake_requested = Arc::new(AtomicBool::new(false)); let mut builder = ServoBuilder::default() @@ -109,6 +150,7 @@ impl SoftwareServoHost { Ok(Self { servo, default_surface_size: size, + rendering_context_kind, webviews: HashMap::new(), permissions: Rc::new(RefCell::new(HashMap::new())), wake_requested, @@ -335,7 +377,7 @@ impl SoftwareServoHost { size: ServoSurfaceSize, ) -> Result { let webview_id = WebViewId::new(); - let rendering_context = Self::new_rendering_context(size)?; + let rendering_context = self.new_rendering_context(size)?; let delegate = Rc::new(HostWebViewDelegate::new(profile_id.clone(), self.permissions.clone())); let webview = WebViewBuilder::new(&self.servo, rendering_context.clone()) @@ -365,12 +407,24 @@ impl SoftwareServoHost { } fn new_rendering_context( + &self, size: ServoSurfaceSize, ) -> Result, ServoHostError> { - let rendering_context = Rc::new( - servo::SoftwareRenderingContext::new(size.physical()) - .map_err(|_| ServoHostError::RenderingContextUnavailable)?, - ); + let rendering_context: Rc = match self.rendering_context_kind { + RenderingContextKind::Software => Rc::new( + servo::SoftwareRenderingContext::new(size.physical()) + .map_err(|_| ServoHostError::RenderingContextUnavailable)?, + ), + #[cfg(feature = "hardware-render")] + RenderingContextKind::Hardware => Rc::new( + crate::HardwareOffscreenContext::new(size.physical()) + .map_err(|_| ServoHostError::RenderingContextUnavailable)?, + ), + #[cfg(not(feature = "hardware-render"))] + RenderingContextKind::Hardware => { + return Err(ServoHostError::HardwareRenderUnavailable); + } + }; rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?; Ok(rendering_context) }