Wire the vendored hardware context into SoftwareServoHost

`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<u8>` 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.
This commit is contained in:
2026-05-10 20:55:35 -04:00
parent 048c5dfecd
commit a7d3e896bb
3 changed files with 68 additions and 8 deletions
+6
View File
@@ -24,6 +24,12 @@ pub enum ServoHostError {
#[error("servo rendering context could not be made current")] #[error("servo rendering context could not be made current")]
RenderingContextNotCurrent, 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")] #[error("servo rendered frame is unavailable")]
RenderedFrameUnavailable, RenderedFrameUnavailable,
+1 -1
View File
@@ -25,4 +25,4 @@ pub use host::{
TouchTapRequest, WebViewSnapshot, WebViewState, TouchTapRequest, WebViewSnapshot, WebViewState,
}; };
#[cfg(feature = "servo-engine")] #[cfg(feature = "servo-engine")]
pub use runtime::{ServoSurfaceSize, SoftwareServoHost}; pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost};
+59 -5
View File
@@ -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 { pub struct SoftwareServoHost {
servo: Servo, servo: Servo,
default_surface_size: ServoSurfaceSize, default_surface_size: ServoSurfaceSize,
rendering_context_kind: RenderingContextKind,
webviews: HashMap<WebViewId, HostWebView>, webviews: HashMap<WebViewId, HostWebView>,
permissions: PermissionStore, permissions: PermissionStore,
wake_requested: Arc<AtomicBool>, wake_requested: Arc<AtomicBool>,
@@ -64,13 +82,35 @@ pub struct SoftwareServoHost {
impl SoftwareServoHost { impl SoftwareServoHost {
pub fn new(size: ServoSurfaceSize) -> Result<Self, ServoHostError> { pub fn new(size: ServoSurfaceSize) -> Result<Self, ServoHostError> {
Self::new_with_config_dir(size, None) Self::new_with_config_dir_and_kind(size, None, RenderingContextKind::Software)
} }
pub fn new_with_config_dir( pub fn new_with_config_dir(
size: ServoSurfaceSize, size: ServoSurfaceSize,
config_dir: Option<PathBuf>, config_dir: Option<PathBuf>,
) -> Result<Self, ServoHostError> { ) -> Result<Self, ServoHostError> {
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<PathBuf>,
rendering_context_kind: RenderingContextKind,
) -> Result<Self, ServoHostError> {
if rendering_context_kind == RenderingContextKind::Hardware
&& !cfg!(feature = "hardware-render")
{
return Err(ServoHostError::HardwareRenderUnavailable);
}
if SERVO_RUNTIME_STARTED if SERVO_RUNTIME_STARTED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err() .is_err()
@@ -78,7 +118,7 @@ impl SoftwareServoHost {
return Err(ServoHostError::RuntimeAlreadyStarted); 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() { if host.is_err() {
SERVO_RUNTIME_STARTED.store(false, Ordering::Release); SERVO_RUNTIME_STARTED.store(false, Ordering::Release);
} }
@@ -97,6 +137,7 @@ impl SoftwareServoHost {
fn new_started( fn new_started(
size: ServoSurfaceSize, size: ServoSurfaceSize,
config_dir: Option<PathBuf>, config_dir: Option<PathBuf>,
rendering_context_kind: RenderingContextKind,
) -> Result<Self, ServoHostError> { ) -> Result<Self, ServoHostError> {
let wake_requested = Arc::new(AtomicBool::new(false)); let wake_requested = Arc::new(AtomicBool::new(false));
let mut builder = ServoBuilder::default() let mut builder = ServoBuilder::default()
@@ -109,6 +150,7 @@ impl SoftwareServoHost {
Ok(Self { Ok(Self {
servo, servo,
default_surface_size: size, default_surface_size: size,
rendering_context_kind,
webviews: HashMap::new(), webviews: HashMap::new(),
permissions: Rc::new(RefCell::new(HashMap::new())), permissions: Rc::new(RefCell::new(HashMap::new())),
wake_requested, wake_requested,
@@ -335,7 +377,7 @@ impl SoftwareServoHost {
size: ServoSurfaceSize, size: ServoSurfaceSize,
) -> Result<WebViewId, ServoHostError> { ) -> Result<WebViewId, ServoHostError> {
let webview_id = WebViewId::new(); let webview_id = WebViewId::new();
let rendering_context = Self::new_rendering_context(size)?; let rendering_context = self.new_rendering_context(size)?;
let delegate = let delegate =
Rc::new(HostWebViewDelegate::new(profile_id.clone(), self.permissions.clone())); Rc::new(HostWebViewDelegate::new(profile_id.clone(), self.permissions.clone()));
let webview = WebViewBuilder::new(&self.servo, rendering_context.clone()) let webview = WebViewBuilder::new(&self.servo, rendering_context.clone())
@@ -365,12 +407,24 @@ impl SoftwareServoHost {
} }
fn new_rendering_context( fn new_rendering_context(
&self,
size: ServoSurfaceSize, size: ServoSurfaceSize,
) -> Result<Rc<dyn RenderingContext>, ServoHostError> { ) -> Result<Rc<dyn RenderingContext>, ServoHostError> {
let rendering_context = Rc::new( let rendering_context: Rc<dyn RenderingContext> = match self.rendering_context_kind {
RenderingContextKind::Software => Rc::new(
servo::SoftwareRenderingContext::new(size.physical()) servo::SoftwareRenderingContext::new(size.physical())
.map_err(|_| ServoHostError::RenderingContextUnavailable)?, .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)?; rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
Ok(rendering_context) Ok(rendering_context)
} }