mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
compositing
: Combine webrender_traits
and compositing_traits
(#36372)
These two traits both exposed different parts of the compositing API, but now that the compositor doesn't depend directly on `script` any longer and the `script_traits` crate has been split into the `constellation_traits` crate, this can be finally be cleaned up without causing circular dependencies. In addition, some unit tests for the `IOPCompositor`'s scroll node tree are also moved into `compositing_traits` as well. Testing: This just combines two crates, so no new tests are necessary. Fixes: #35984. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
e74a042efd
commit
0caa271176
56 changed files with 582 additions and 637 deletions
|
@ -11,16 +11,27 @@ rust-version.workspace = true
|
|||
name = "compositing_traits"
|
||||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
no-wgl = ["surfman/sm-angle-default"]
|
||||
|
||||
[dependencies]
|
||||
base = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
dpi = { version = "0.1" }
|
||||
embedder_traits = { workspace = true }
|
||||
euclid = { workspace = true }
|
||||
gleam = { workspace = true }
|
||||
glow = { workspace = true }
|
||||
image = { workspace = true }
|
||||
ipc-channel = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pixels = { path = '../../pixels' }
|
||||
script_traits = { workspace = true }
|
||||
raw-window-handle = { version = "0.6" }
|
||||
serde = { workspace = true }
|
||||
servo_geometry = { path = "../../geometry" }
|
||||
strum_macros = { workspace = true }
|
||||
stylo = { workspace = true }
|
||||
stylo_traits = { workspace = true }
|
||||
surfman = { workspace = true, features = ["sm-x11"] }
|
||||
webrender_api = { workspace = true }
|
||||
webrender_traits = { workspace = true }
|
||||
|
||||
|
|
373
components/shared/compositing/display_list.rs
Normal file
373
components/shared/compositing/display_list.rs
Normal file
|
@ -0,0 +1,373 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Defines data structures which are consumed by the Compositor.
|
||||
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use embedder_traits::Cursor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use style::values::specified::Overflow;
|
||||
use webrender_api::units::{LayoutSize, LayoutVector2D};
|
||||
use webrender_api::{Epoch, ExternalScrollId, PipelineId, ScrollLocation, SpatialId};
|
||||
|
||||
/// The scroll sensitivity of a scroll node in a particular axis ie whether it can be scrolled due to
|
||||
/// input events and script events or only script events.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum ScrollSensitivity {
|
||||
/// This node can be scrolled by input and script events.
|
||||
ScriptAndInputEvents,
|
||||
/// This node can only be scrolled by script events.
|
||||
Script,
|
||||
/// This node cannot be scrolled.
|
||||
None,
|
||||
}
|
||||
|
||||
/// Convert [Overflow] to [ScrollSensitivity].
|
||||
impl From<Overflow> for ScrollSensitivity {
|
||||
fn from(overflow: Overflow) -> Self {
|
||||
match overflow {
|
||||
Overflow::Hidden => ScrollSensitivity::Script,
|
||||
Overflow::Scroll | Overflow::Auto => ScrollSensitivity::ScriptAndInputEvents,
|
||||
Overflow::Visible | Overflow::Clip => ScrollSensitivity::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [ScrollSensitivity] of particular node in the vertical and horizontal axes.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct AxesScrollSensitivity {
|
||||
pub x: ScrollSensitivity,
|
||||
pub y: ScrollSensitivity,
|
||||
}
|
||||
|
||||
/// Information that Servo keeps alongside WebRender display items
|
||||
/// in order to add more context to hit test results.
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct HitTestInfo {
|
||||
/// The id of the node of this hit test item.
|
||||
pub node: u64,
|
||||
|
||||
/// The cursor of this node's hit test item.
|
||||
pub cursor: Option<Cursor>,
|
||||
|
||||
/// The id of the [ScrollTree] associated with this hit test item.
|
||||
pub scroll_tree_node: ScrollTreeNodeId,
|
||||
}
|
||||
|
||||
/// Data stored for nodes in the [ScrollTree] that actually scroll,
|
||||
/// as opposed to reference frames and sticky nodes which do not.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ScrollableNodeInfo {
|
||||
/// The external scroll id of this node, used to track
|
||||
/// it between successive re-layouts.
|
||||
pub external_id: ExternalScrollId,
|
||||
|
||||
/// Amount that this `ScrollableNode` can scroll in both directions.
|
||||
pub scrollable_size: LayoutSize,
|
||||
|
||||
/// Whether this `ScrollableNode` is sensitive to input events.
|
||||
pub scroll_sensitivity: AxesScrollSensitivity,
|
||||
|
||||
/// The current offset of this scroll node.
|
||||
pub offset: LayoutVector2D,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
/// A node in a tree of scroll nodes. This may either be a scrollable
|
||||
/// node which responds to scroll events or a non-scrollable one.
|
||||
pub struct ScrollTreeNode {
|
||||
/// The index of the parent of this node in the tree. If this is
|
||||
/// None then this is the root node.
|
||||
pub parent: Option<ScrollTreeNodeId>,
|
||||
|
||||
/// Scrolling data which will not be None if this is a scrolling node.
|
||||
pub scroll_info: Option<ScrollableNodeInfo>,
|
||||
}
|
||||
|
||||
impl ScrollTreeNode {
|
||||
/// Get the external id of this node.
|
||||
pub fn external_id(&self) -> Option<ExternalScrollId> {
|
||||
self.scroll_info.as_ref().map(|info| info.external_id)
|
||||
}
|
||||
|
||||
/// Get the offset id of this node if it applies.
|
||||
pub fn offset(&self) -> Option<LayoutVector2D> {
|
||||
self.scroll_info.as_ref().map(|info| info.offset)
|
||||
}
|
||||
|
||||
/// Set the offset for this node, returns false if this was a
|
||||
/// non-scrolling node for which you cannot set the offset.
|
||||
pub fn set_offset(&mut self, new_offset: LayoutVector2D) -> bool {
|
||||
match self.scroll_info {
|
||||
Some(ref mut info) => {
|
||||
let scrollable_width = info.scrollable_size.width;
|
||||
let scrollable_height = info.scrollable_size.height;
|
||||
|
||||
if scrollable_width > 0. {
|
||||
info.offset.x = (new_offset.x).min(0.0).max(-scrollable_width);
|
||||
}
|
||||
|
||||
if scrollable_height > 0. {
|
||||
info.offset.y = (new_offset.y).min(0.0).max(-scrollable_height);
|
||||
}
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Scroll this node given a WebRender ScrollLocation. Returns a tuple that can
|
||||
/// be used to scroll an individual WebRender scroll frame if the operation
|
||||
/// actually changed an offset.
|
||||
pub fn scroll(
|
||||
&mut self,
|
||||
scroll_location: ScrollLocation,
|
||||
) -> Option<(ExternalScrollId, LayoutVector2D)> {
|
||||
let info = match self.scroll_info {
|
||||
Some(ref mut data) => data,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
if info.scroll_sensitivity.x != ScrollSensitivity::ScriptAndInputEvents &&
|
||||
info.scroll_sensitivity.y != ScrollSensitivity::ScriptAndInputEvents
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let delta = match scroll_location {
|
||||
ScrollLocation::Delta(delta) => delta,
|
||||
ScrollLocation::Start => {
|
||||
if info.offset.y.round() >= 0.0 {
|
||||
// Nothing to do on this layer.
|
||||
return None;
|
||||
}
|
||||
|
||||
info.offset.y = 0.0;
|
||||
return Some((info.external_id, info.offset));
|
||||
},
|
||||
ScrollLocation::End => {
|
||||
let end_pos = -info.scrollable_size.height;
|
||||
if info.offset.y.round() <= end_pos {
|
||||
// Nothing to do on this layer.
|
||||
return None;
|
||||
}
|
||||
|
||||
info.offset.y = end_pos;
|
||||
return Some((info.external_id, info.offset));
|
||||
},
|
||||
};
|
||||
|
||||
let scrollable_width = info.scrollable_size.width;
|
||||
let scrollable_height = info.scrollable_size.height;
|
||||
let original_layer_scroll_offset = info.offset;
|
||||
|
||||
if scrollable_width > 0. &&
|
||||
info.scroll_sensitivity.x == ScrollSensitivity::ScriptAndInputEvents
|
||||
{
|
||||
info.offset.x = (info.offset.x + delta.x).min(0.0).max(-scrollable_width);
|
||||
}
|
||||
|
||||
if scrollable_height > 0. &&
|
||||
info.scroll_sensitivity.y == ScrollSensitivity::ScriptAndInputEvents
|
||||
{
|
||||
info.offset.y = (info.offset.y + delta.y).min(0.0).max(-scrollable_height);
|
||||
}
|
||||
|
||||
if info.offset != original_layer_scroll_offset {
|
||||
Some((info.external_id, info.offset))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender
|
||||
/// display list, except these are used to scrolling in the compositor so that
|
||||
/// new offsets can be sent to WebRender.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct ScrollTree {
|
||||
/// A list of compositor-side scroll nodes that describe the tree
|
||||
/// of WebRender spatial nodes, used by the compositor to scroll the
|
||||
/// contents of the display list.
|
||||
pub nodes: Vec<ScrollTreeNode>,
|
||||
}
|
||||
|
||||
impl ScrollTree {
|
||||
/// Add a scroll node to this ScrollTree returning the id of the new node.
|
||||
pub fn add_scroll_tree_node(
|
||||
&mut self,
|
||||
parent: Option<&ScrollTreeNodeId>,
|
||||
spatial_id: SpatialId,
|
||||
scroll_info: Option<ScrollableNodeInfo>,
|
||||
) -> ScrollTreeNodeId {
|
||||
self.nodes.push(ScrollTreeNode {
|
||||
parent: parent.cloned(),
|
||||
scroll_info,
|
||||
});
|
||||
ScrollTreeNodeId {
|
||||
index: self.nodes.len() - 1,
|
||||
spatial_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the node with the given index.
|
||||
pub fn get_node_mut(&mut self, id: &ScrollTreeNodeId) -> &mut ScrollTreeNode {
|
||||
&mut self.nodes[id.index]
|
||||
}
|
||||
|
||||
/// Get an immutable reference to the node with the given index.
|
||||
pub fn get_node(&mut self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
|
||||
&self.nodes[id.index]
|
||||
}
|
||||
|
||||
/// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled,
|
||||
/// because it isn't a scrollable node or it's already scrolled to the maximum scroll
|
||||
/// extent, try to scroll an ancestor of this node. Returns the node scrolled and the
|
||||
/// new offset if a scroll was performed, otherwise returns None.
|
||||
pub fn scroll_node_or_ancestor(
|
||||
&mut self,
|
||||
scroll_node_id: &ScrollTreeNodeId,
|
||||
scroll_location: ScrollLocation,
|
||||
) -> Option<(ExternalScrollId, LayoutVector2D)> {
|
||||
let parent = {
|
||||
let node = &mut self.get_node_mut(scroll_node_id);
|
||||
let result = node.scroll(scroll_location);
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
node.parent
|
||||
};
|
||||
|
||||
parent.and_then(|parent| self.scroll_node_or_ancestor(&parent, scroll_location))
|
||||
}
|
||||
|
||||
/// Given an [`ExternalScrollId`] and an offset, update the scroll offset of the scroll node
|
||||
/// with the given id.
|
||||
pub fn set_scroll_offsets_for_node_with_external_scroll_id(
|
||||
&mut self,
|
||||
external_scroll_id: ExternalScrollId,
|
||||
offset: LayoutVector2D,
|
||||
) -> bool {
|
||||
for node in self.nodes.iter_mut() {
|
||||
match node.scroll_info {
|
||||
Some(ref mut scroll_info) if scroll_info.external_id == external_scroll_id => {
|
||||
scroll_info.offset = offset;
|
||||
return true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A data structure which stores compositor-side information about
|
||||
/// display lists sent to the compositor.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct CompositorDisplayListInfo {
|
||||
/// The WebRender [PipelineId] of this display list.
|
||||
pub pipeline_id: PipelineId,
|
||||
|
||||
/// The size of the viewport that this display list renders into.
|
||||
pub viewport_size: LayoutSize,
|
||||
|
||||
/// The size of this display list's content.
|
||||
pub content_size: LayoutSize,
|
||||
|
||||
/// The epoch of the display list.
|
||||
pub epoch: Epoch,
|
||||
|
||||
/// An array of `HitTestInfo` which is used to store information
|
||||
/// to assist the compositor to take various actions (set the cursor,
|
||||
/// scroll without layout) using a WebRender hit test result.
|
||||
pub hit_test_info: Vec<HitTestInfo>,
|
||||
|
||||
/// A ScrollTree used by the compositor to scroll the contents of the
|
||||
/// display list.
|
||||
pub scroll_tree: ScrollTree,
|
||||
|
||||
/// The `ScrollTreeNodeId` of the root reference frame of this info's scroll
|
||||
/// tree.
|
||||
pub root_reference_frame_id: ScrollTreeNodeId,
|
||||
|
||||
/// The `ScrollTreeNodeId` of the topmost scrolling frame of this info's scroll
|
||||
/// tree.
|
||||
pub root_scroll_node_id: ScrollTreeNodeId,
|
||||
|
||||
/// Contentful paint i.e. whether the display list contains items of type
|
||||
/// text, image, non-white canvas or SVG). Used by metrics.
|
||||
/// See <https://w3c.github.io/paint-timing/#first-contentful-paint>.
|
||||
pub is_contentful: bool,
|
||||
|
||||
/// Whether the first layout or a subsequent (incremental) layout triggered this
|
||||
/// display list creation.
|
||||
pub first_reflow: bool,
|
||||
}
|
||||
|
||||
impl CompositorDisplayListInfo {
|
||||
/// Create a new CompositorDisplayListInfo with the root reference frame
|
||||
/// and scroll frame already added to the scroll tree.
|
||||
pub fn new(
|
||||
viewport_size: LayoutSize,
|
||||
content_size: LayoutSize,
|
||||
pipeline_id: PipelineId,
|
||||
epoch: Epoch,
|
||||
viewport_scroll_sensitivity: AxesScrollSensitivity,
|
||||
first_reflow: bool,
|
||||
) -> Self {
|
||||
let mut scroll_tree = ScrollTree::default();
|
||||
let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
|
||||
None,
|
||||
SpatialId::root_reference_frame(pipeline_id),
|
||||
None,
|
||||
);
|
||||
let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
|
||||
Some(&root_reference_frame_id),
|
||||
SpatialId::root_scroll_node(pipeline_id),
|
||||
Some(ScrollableNodeInfo {
|
||||
external_id: ExternalScrollId(0, pipeline_id),
|
||||
scrollable_size: content_size - viewport_size,
|
||||
scroll_sensitivity: viewport_scroll_sensitivity,
|
||||
offset: LayoutVector2D::zero(),
|
||||
}),
|
||||
);
|
||||
|
||||
CompositorDisplayListInfo {
|
||||
pipeline_id,
|
||||
viewport_size,
|
||||
content_size,
|
||||
epoch,
|
||||
hit_test_info: Default::default(),
|
||||
scroll_tree,
|
||||
root_reference_frame_id,
|
||||
root_scroll_node_id,
|
||||
is_contentful: false,
|
||||
first_reflow,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or re-use a duplicate HitTestInfo entry in this `CompositorHitTestInfo`
|
||||
/// and return the index.
|
||||
pub fn add_hit_test_info(
|
||||
&mut self,
|
||||
node: u64,
|
||||
cursor: Option<Cursor>,
|
||||
scroll_tree_node: ScrollTreeNodeId,
|
||||
) -> usize {
|
||||
let hit_test_info = HitTestInfo {
|
||||
node,
|
||||
cursor,
|
||||
scroll_tree_node,
|
||||
};
|
||||
|
||||
if let Some(last) = self.hit_test_info.last() {
|
||||
if hit_test_info == *last {
|
||||
return self.hit_test_info.len() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.hit_test_info.push(hit_test_info);
|
||||
self.hit_test_info.len() - 1
|
||||
}
|
||||
}
|
|
@ -18,7 +18,27 @@ use pixels::Image;
|
|||
use strum_macros::IntoStaticStr;
|
||||
use style_traits::CSSPixel;
|
||||
use webrender_api::DocumentId;
|
||||
use webrender_traits::{CrossProcessCompositorApi, CrossProcessCompositorMessage};
|
||||
|
||||
pub mod display_list;
|
||||
pub mod rendering_context;
|
||||
|
||||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use display_list::CompositorDisplayListInfo;
|
||||
use embedder_traits::{CompositorHitTestResult, ScreenGeometry};
|
||||
use euclid::default::Size2D as UntypedSize2D;
|
||||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize};
|
||||
use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect};
|
||||
use webrender_api::{
|
||||
BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData,
|
||||
ExternalImageHandler, ExternalImageId, ExternalImageSource, ExternalScrollId,
|
||||
FontInstanceFlags, FontInstanceKey, FontKey, HitTestFlags, ImageData, ImageDescriptor,
|
||||
ImageKey, NativeFontHandle, PipelineId as WebRenderPipelineId,
|
||||
};
|
||||
|
||||
/// Sends messages to the compositor.
|
||||
#[derive(Clone)]
|
||||
|
@ -111,3 +131,463 @@ impl Debug for CompositorMsg {
|
|||
write!(formatter, "{string}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum CrossProcessCompositorMessage {
|
||||
/// Inform WebRender of the existence of this pipeline.
|
||||
SendInitialTransaction(WebRenderPipelineId),
|
||||
/// Perform a scroll operation.
|
||||
SendScrollNode(
|
||||
WebViewId,
|
||||
WebRenderPipelineId,
|
||||
LayoutPoint,
|
||||
ExternalScrollId,
|
||||
),
|
||||
/// Inform WebRender of a new display list for the given pipeline.
|
||||
SendDisplayList {
|
||||
/// The [`WebViewId`] that this display list belongs to.
|
||||
webview_id: WebViewId,
|
||||
/// The [CompositorDisplayListInfo] that describes the display list being sent.
|
||||
display_list_info: Box<CompositorDisplayListInfo>,
|
||||
/// A descriptor of this display list used to construct this display list from raw data.
|
||||
display_list_descriptor: BuiltDisplayListDescriptor,
|
||||
/// An [ipc::IpcBytesReceiver] used to send the raw data of the display list.
|
||||
display_list_receiver: ipc::IpcBytesReceiver,
|
||||
},
|
||||
/// Perform a hit test operation. The result will be returned via
|
||||
/// the provided channel sender.
|
||||
HitTest(
|
||||
Option<WebRenderPipelineId>,
|
||||
DevicePoint,
|
||||
HitTestFlags,
|
||||
IpcSender<Vec<CompositorHitTestResult>>,
|
||||
),
|
||||
/// Create a new image key. The result will be returned via the
|
||||
/// provided channel sender.
|
||||
GenerateImageKey(IpcSender<ImageKey>),
|
||||
/// Add an image with the given data and `ImageKey`.
|
||||
AddImage(ImageKey, ImageDescriptor, SerializableImageData),
|
||||
/// Perform a resource update operation.
|
||||
UpdateImages(Vec<ImageUpdate>),
|
||||
|
||||
/// Generate a new batch of font keys which can be used to allocate
|
||||
/// keys asynchronously.
|
||||
GenerateFontKeys(
|
||||
usize,
|
||||
usize,
|
||||
IpcSender<(Vec<FontKey>, Vec<FontInstanceKey>)>,
|
||||
),
|
||||
/// Add a font with the given data and font key.
|
||||
AddFont(FontKey, Arc<IpcSharedMemory>, u32),
|
||||
/// Add a system font with the given font key and handle.
|
||||
AddSystemFont(FontKey, NativeFontHandle),
|
||||
/// Add an instance of a font with the given instance key.
|
||||
AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
|
||||
/// Remove the given font resources from our WebRender instance.
|
||||
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
|
||||
|
||||
/// Get the client window size and position.
|
||||
GetClientWindowRect(WebViewId, IpcSender<DeviceIndependentIntRect>),
|
||||
/// Get the size of the screen that the client window inhabits.
|
||||
GetScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
|
||||
/// Get the available screen size (without toolbars and docks) for the screen
|
||||
/// the client window inhabits.
|
||||
GetAvailableScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for CrossProcessCompositorMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::AddImage(..) => f.write_str("AddImage"),
|
||||
Self::GenerateFontKeys(..) => f.write_str("GenerateFontKeys"),
|
||||
Self::AddSystemFont(..) => f.write_str("AddSystemFont"),
|
||||
Self::SendInitialTransaction(..) => f.write_str("SendInitialTransaction"),
|
||||
Self::SendScrollNode(..) => f.write_str("SendScrollNode"),
|
||||
Self::SendDisplayList { .. } => f.write_str("SendDisplayList"),
|
||||
Self::HitTest(..) => f.write_str("HitTest"),
|
||||
Self::GenerateImageKey(..) => f.write_str("GenerateImageKey"),
|
||||
Self::UpdateImages(..) => f.write_str("UpdateImages"),
|
||||
Self::RemoveFonts(..) => f.write_str("RemoveFonts"),
|
||||
Self::AddFontInstance(..) => f.write_str("AddFontInstance"),
|
||||
Self::AddFont(..) => f.write_str("AddFont"),
|
||||
Self::GetClientWindowRect(..) => f.write_str("GetClientWindowRect"),
|
||||
Self::GetScreenSize(..) => f.write_str("GetScreenSize"),
|
||||
Self::GetAvailableScreenSize(..) => f.write_str("GetAvailableScreenSize"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mechanism to send messages from ScriptThread to the parent process' WebRender instance.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct CrossProcessCompositorApi(pub IpcSender<CrossProcessCompositorMessage>);
|
||||
|
||||
impl CrossProcessCompositorApi {
|
||||
/// Create a new [`CrossProcessCompositorApi`] struct that does not have a listener on the other
|
||||
/// end to use for unit testing.
|
||||
pub fn dummy() -> Self {
|
||||
let (sender, _) = ipc::channel().unwrap();
|
||||
Self(sender)
|
||||
}
|
||||
|
||||
/// Get the sender for this proxy.
|
||||
pub fn sender(&self) -> &IpcSender<CrossProcessCompositorMessage> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Inform WebRender of the existence of this pipeline.
|
||||
pub fn send_initial_transaction(&self, pipeline: WebRenderPipelineId) {
|
||||
if let Err(e) = self
|
||||
.0
|
||||
.send(CrossProcessCompositorMessage::SendInitialTransaction(
|
||||
pipeline,
|
||||
))
|
||||
{
|
||||
warn!("Error sending initial transaction: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a scroll operation.
|
||||
pub fn send_scroll_node(
|
||||
&self,
|
||||
webview_id: WebViewId,
|
||||
pipeline_id: WebRenderPipelineId,
|
||||
point: LayoutPoint,
|
||||
scroll_id: ExternalScrollId,
|
||||
) {
|
||||
if let Err(e) = self.0.send(CrossProcessCompositorMessage::SendScrollNode(
|
||||
webview_id,
|
||||
pipeline_id,
|
||||
point,
|
||||
scroll_id,
|
||||
)) {
|
||||
warn!("Error sending scroll node: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inform WebRender of a new display list for the given pipeline.
|
||||
pub fn send_display_list(
|
||||
&self,
|
||||
webview_id: WebViewId,
|
||||
display_list_info: CompositorDisplayListInfo,
|
||||
list: BuiltDisplayList,
|
||||
) {
|
||||
let (display_list_data, display_list_descriptor) = list.into_data();
|
||||
let (display_list_sender, display_list_receiver) = ipc::bytes_channel().unwrap();
|
||||
if let Err(e) = self.0.send(CrossProcessCompositorMessage::SendDisplayList {
|
||||
webview_id,
|
||||
display_list_info: Box::new(display_list_info),
|
||||
display_list_descriptor,
|
||||
display_list_receiver,
|
||||
}) {
|
||||
warn!("Error sending display list: {}", e);
|
||||
}
|
||||
|
||||
if let Err(error) = display_list_sender.send(&display_list_data.items_data) {
|
||||
warn!("Error sending display list items: {}", error);
|
||||
}
|
||||
if let Err(error) = display_list_sender.send(&display_list_data.cache_data) {
|
||||
warn!("Error sending display list cache data: {}", error);
|
||||
}
|
||||
if let Err(error) = display_list_sender.send(&display_list_data.spatial_tree) {
|
||||
warn!("Error sending display spatial tree: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a hit test operation. Blocks until the operation is complete and
|
||||
/// and a result is available.
|
||||
pub fn hit_test(
|
||||
&self,
|
||||
pipeline: Option<WebRenderPipelineId>,
|
||||
point: DevicePoint,
|
||||
flags: HitTestFlags,
|
||||
) -> Vec<CompositorHitTestResult> {
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
self.0
|
||||
.send(CrossProcessCompositorMessage::HitTest(
|
||||
pipeline, point, flags, sender,
|
||||
))
|
||||
.expect("error sending hit test");
|
||||
receiver.recv().expect("error receiving hit test result")
|
||||
}
|
||||
|
||||
/// Create a new image key. Blocks until the key is available.
|
||||
pub fn generate_image_key(&self) -> Option<ImageKey> {
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
self.0
|
||||
.send(CrossProcessCompositorMessage::GenerateImageKey(sender))
|
||||
.ok()?;
|
||||
receiver.recv().ok()
|
||||
}
|
||||
|
||||
pub fn add_image(
|
||||
&self,
|
||||
key: ImageKey,
|
||||
descriptor: ImageDescriptor,
|
||||
data: SerializableImageData,
|
||||
) {
|
||||
if let Err(e) = self.0.send(CrossProcessCompositorMessage::AddImage(
|
||||
key, descriptor, data,
|
||||
)) {
|
||||
warn!("Error sending image update: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform an image resource update operation.
|
||||
pub fn update_images(&self, updates: Vec<ImageUpdate>) {
|
||||
if let Err(e) = self
|
||||
.0
|
||||
.send(CrossProcessCompositorMessage::UpdateImages(updates))
|
||||
{
|
||||
warn!("error sending image updates: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_unused_font_resources(
|
||||
&self,
|
||||
keys: Vec<FontKey>,
|
||||
instance_keys: Vec<FontInstanceKey>,
|
||||
) {
|
||||
if keys.is_empty() && instance_keys.is_empty() {
|
||||
return;
|
||||
}
|
||||
let _ = self.0.send(CrossProcessCompositorMessage::RemoveFonts(
|
||||
keys,
|
||||
instance_keys,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn add_font_instance(
|
||||
&self,
|
||||
font_instance_key: FontInstanceKey,
|
||||
font_key: FontKey,
|
||||
size: f32,
|
||||
flags: FontInstanceFlags,
|
||||
) {
|
||||
let _x = self.0.send(CrossProcessCompositorMessage::AddFontInstance(
|
||||
font_instance_key,
|
||||
font_key,
|
||||
size,
|
||||
flags,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn add_font(&self, font_key: FontKey, data: Arc<IpcSharedMemory>, index: u32) {
|
||||
let _ = self.0.send(CrossProcessCompositorMessage::AddFont(
|
||||
font_key, data, index,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn add_system_font(&self, font_key: FontKey, handle: NativeFontHandle) {
|
||||
let _ = self.0.send(CrossProcessCompositorMessage::AddSystemFont(
|
||||
font_key, handle,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn fetch_font_keys(
|
||||
&self,
|
||||
number_of_font_keys: usize,
|
||||
number_of_font_instance_keys: usize,
|
||||
) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
|
||||
let (sender, receiver) = ipc_channel::ipc::channel().expect("Could not create IPC channel");
|
||||
let _ = self.0.send(CrossProcessCompositorMessage::GenerateFontKeys(
|
||||
number_of_font_keys,
|
||||
number_of_font_instance_keys,
|
||||
sender,
|
||||
));
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is used as a bridge between the different GL clients
|
||||
/// in Servo that handles WebRender ExternalImages and the WebRender
|
||||
/// ExternalImageHandler API.
|
||||
//
|
||||
/// This trait is used to notify lock/unlock messages and get the
|
||||
/// required info that WR needs.
|
||||
pub trait WebrenderExternalImageApi {
|
||||
fn lock(&mut self, id: u64) -> (WebrenderImageSource, UntypedSize2D<i32>);
|
||||
fn unlock(&mut self, id: u64);
|
||||
}
|
||||
|
||||
pub enum WebrenderImageSource<'a> {
|
||||
TextureHandle(u32),
|
||||
Raw(&'a [u8]),
|
||||
}
|
||||
|
||||
/// Type of Webrender External Image Handler.
|
||||
pub enum WebrenderImageHandlerType {
|
||||
WebGL,
|
||||
Media,
|
||||
WebGPU,
|
||||
}
|
||||
|
||||
/// List of Webrender external images to be shared among all external image
|
||||
/// consumers (WebGL, Media, WebGPU).
|
||||
/// It ensures that external image identifiers are unique.
|
||||
#[derive(Default)]
|
||||
pub struct WebrenderExternalImageRegistry {
|
||||
/// Map of all generated external images.
|
||||
external_images: HashMap<ExternalImageId, WebrenderImageHandlerType>,
|
||||
/// Id generator for the next external image identifier.
|
||||
next_image_id: u64,
|
||||
}
|
||||
|
||||
impl WebrenderExternalImageRegistry {
|
||||
pub fn next_id(&mut self, handler_type: WebrenderImageHandlerType) -> ExternalImageId {
|
||||
self.next_image_id += 1;
|
||||
let key = ExternalImageId(self.next_image_id);
|
||||
self.external_images.insert(key, handler_type);
|
||||
key
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &ExternalImageId) {
|
||||
self.external_images.remove(key);
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &ExternalImageId) -> Option<&WebrenderImageHandlerType> {
|
||||
self.external_images.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// WebRender External Image Handler implementation.
|
||||
pub struct WebrenderExternalImageHandlers {
|
||||
/// WebGL handler.
|
||||
webgl_handler: Option<Box<dyn WebrenderExternalImageApi>>,
|
||||
/// Media player handler.
|
||||
media_handler: Option<Box<dyn WebrenderExternalImageApi>>,
|
||||
/// WebGPU handler.
|
||||
webgpu_handler: Option<Box<dyn WebrenderExternalImageApi>>,
|
||||
/// Webrender external images.
|
||||
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
|
||||
}
|
||||
|
||||
impl WebrenderExternalImageHandlers {
|
||||
pub fn new() -> (Self, Arc<Mutex<WebrenderExternalImageRegistry>>) {
|
||||
let external_images = Arc::new(Mutex::new(WebrenderExternalImageRegistry::default()));
|
||||
(
|
||||
Self {
|
||||
webgl_handler: None,
|
||||
media_handler: None,
|
||||
webgpu_handler: None,
|
||||
external_images: external_images.clone(),
|
||||
},
|
||||
external_images,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_handler(
|
||||
&mut self,
|
||||
handler: Box<dyn WebrenderExternalImageApi>,
|
||||
handler_type: WebrenderImageHandlerType,
|
||||
) {
|
||||
match handler_type {
|
||||
WebrenderImageHandlerType::WebGL => self.webgl_handler = Some(handler),
|
||||
WebrenderImageHandlerType::Media => self.media_handler = Some(handler),
|
||||
WebrenderImageHandlerType::WebGPU => self.webgpu_handler = Some(handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalImageHandler for WebrenderExternalImageHandlers {
|
||||
/// Lock the external image. Then, WR could start to read the
|
||||
/// image content.
|
||||
/// The WR client should not change the image content until the
|
||||
/// unlock() call.
|
||||
fn lock(&mut self, key: ExternalImageId, _channel_index: u8) -> ExternalImage {
|
||||
let external_images = self.external_images.lock().unwrap();
|
||||
let handler_type = external_images
|
||||
.get(&key)
|
||||
.expect("Tried to get unknown external image");
|
||||
match handler_type {
|
||||
WebrenderImageHandlerType::WebGL => {
|
||||
let (source, size) = self.webgl_handler.as_mut().unwrap().lock(key.0);
|
||||
let texture_id = match source {
|
||||
WebrenderImageSource::TextureHandle(b) => b,
|
||||
_ => panic!("Wrong type"),
|
||||
};
|
||||
ExternalImage {
|
||||
uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0),
|
||||
source: ExternalImageSource::NativeTexture(texture_id),
|
||||
}
|
||||
},
|
||||
WebrenderImageHandlerType::Media => {
|
||||
let (source, size) = self.media_handler.as_mut().unwrap().lock(key.0);
|
||||
let texture_id = match source {
|
||||
WebrenderImageSource::TextureHandle(b) => b,
|
||||
_ => panic!("Wrong type"),
|
||||
};
|
||||
ExternalImage {
|
||||
uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0),
|
||||
source: ExternalImageSource::NativeTexture(texture_id),
|
||||
}
|
||||
},
|
||||
WebrenderImageHandlerType::WebGPU => {
|
||||
let (source, size) = self.webgpu_handler.as_mut().unwrap().lock(key.0);
|
||||
let buffer = match source {
|
||||
WebrenderImageSource::Raw(b) => b,
|
||||
_ => panic!("Wrong type"),
|
||||
};
|
||||
ExternalImage {
|
||||
uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0),
|
||||
source: ExternalImageSource::RawData(buffer),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlock the external image. The WR should not read the image
|
||||
/// content after this call.
|
||||
fn unlock(&mut self, key: ExternalImageId, _channel_index: u8) {
|
||||
let external_images = self.external_images.lock().unwrap();
|
||||
let handler_type = external_images
|
||||
.get(&key)
|
||||
.expect("Tried to get unknown external image");
|
||||
match handler_type {
|
||||
WebrenderImageHandlerType::WebGL => self.webgl_handler.as_mut().unwrap().unlock(key.0),
|
||||
WebrenderImageHandlerType::Media => self.media_handler.as_mut().unwrap().unlock(key.0),
|
||||
WebrenderImageHandlerType::WebGPU => {
|
||||
self.webgpu_handler.as_mut().unwrap().unlock(key.0)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
/// Serializable image updates that must be performed by WebRender.
|
||||
pub enum ImageUpdate {
|
||||
/// Register a new image.
|
||||
AddImage(ImageKey, ImageDescriptor, SerializableImageData),
|
||||
/// Delete a previously registered image registration.
|
||||
DeleteImage(ImageKey),
|
||||
/// Update an existing image registration.
|
||||
UpdateImage(ImageKey, ImageDescriptor, SerializableImageData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
/// Serialized `ImageData`. It contains IPC byte channel receiver to prevent from loading bytes too
|
||||
/// slow.
|
||||
pub enum SerializableImageData {
|
||||
/// A simple series of bytes, provided by the embedding and owned by WebRender.
|
||||
/// The format is stored out-of-band, currently in ImageDescriptor.
|
||||
Raw(IpcSharedMemory),
|
||||
/// An image owned by the embedding, and referenced by WebRender. This may
|
||||
/// take the form of a texture or a heap-allocated buffer.
|
||||
External(ExternalImageData),
|
||||
}
|
||||
|
||||
impl From<SerializableImageData> for ImageData {
|
||||
fn from(value: SerializableImageData) -> Self {
|
||||
match value {
|
||||
SerializableImageData::Raw(shared_memory) => ImageData::new(shared_memory.to_vec()),
|
||||
SerializableImageData::External(image) => ImageData::External(image),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that exposes the embedding layer's `WebView` to the Servo renderer.
|
||||
/// This is to prevent a dependency cycle between the renderer and the embedding
|
||||
/// layer.
|
||||
pub trait RendererWebView {
|
||||
fn id(&self) -> WebViewId;
|
||||
fn screen_geometry(&self) -> Option<ScreenGeometry>;
|
||||
}
|
||||
|
|
887
components/shared/compositing/rendering_context.rs
Normal file
887
components/shared/compositing/rendering_context.rs
Normal file
|
@ -0,0 +1,887 @@
|
|||
/* 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/. */
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use std::cell::{Cell, RefCell, RefMut};
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dpi::PhysicalSize;
|
||||
use euclid::default::{Rect, Size2D as UntypedSize2D};
|
||||
use euclid::{Point2D, Size2D};
|
||||
use gleam::gl::{self, Gl};
|
||||
use glow::NativeFramebuffer;
|
||||
use image::RgbaImage;
|
||||
use log::{debug, trace, warn};
|
||||
use raw_window_handle::{DisplayHandle, WindowHandle};
|
||||
pub use surfman::Error;
|
||||
use surfman::chains::{PreserveBuffer, SwapChain};
|
||||
use surfman::{
|
||||
Adapter, Connection, Context, ContextAttributeFlags, ContextAttributes, Device, GLApi,
|
||||
NativeContext, NativeWidget, Surface, SurfaceAccess, SurfaceInfo, SurfaceTexture, SurfaceType,
|
||||
};
|
||||
use webrender_api::units::{DeviceIntRect, DevicePixel};
|
||||
|
||||
/// 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 {
|
||||
/// 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: DeviceIntRect) -> Option<RgbaImage>;
|
||||
/// Get the current size of this [`RenderingContext`].
|
||||
fn size(&self) -> PhysicalSize<u32>;
|
||||
/// Get the current size of this [`RenderingContext`] as [`Size2D`].
|
||||
fn size2d(&self) -> Size2D<u32, DevicePixel> {
|
||||
let size = self.size();
|
||||
Size2D::new(size.width, size.height)
|
||||
}
|
||||
/// Resizes the rendering surface to the given size.
|
||||
fn resize(&self, size: PhysicalSize<u32>);
|
||||
/// 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 `gleam` version of the OpenGL or GLES API.
|
||||
fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl>;
|
||||
/// Returns the OpenGL or GLES API.
|
||||
fn glow_gl_api(&self) -> Arc<glow::Context>;
|
||||
/// Creates a texture from a given surface and returns the surface texture,
|
||||
/// the OpenGL texture object, and the size of the surface. Default to `None`.
|
||||
fn create_texture(
|
||||
&self,
|
||||
_surface: Surface,
|
||||
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
|
||||
None
|
||||
}
|
||||
/// Destroys the texture and returns the surface. Default to `None`.
|
||||
fn destroy_texture(&self, _surface_texture: SurfaceTexture) -> Option<Surface> {
|
||||
None
|
||||
}
|
||||
/// The connection to the display server for WebGL. Default to `None`.
|
||||
fn connection(&self) -> Option<Connection> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A rendering context that uses the Surfman library to create and manage
|
||||
/// the OpenGL context and surface. This struct provides the default implementation
|
||||
/// of the `RenderingContext` trait, handling the creation, management, and destruction
|
||||
/// of the rendering context and its associated resources.
|
||||
///
|
||||
/// The `SurfmanRenderingContext` struct encapsulates the necessary data and methods
|
||||
/// to interact with the Surfman library, including creating surfaces, binding surfaces,
|
||||
/// resizing surfaces, presenting rendered frames, and managing the OpenGL context state.
|
||||
struct SurfmanRenderingContext {
|
||||
gleam_gl: Rc<dyn Gl>,
|
||||
glow_gl: Arc<glow::Context>,
|
||||
device: RefCell<Device>,
|
||||
context: RefCell<Context>,
|
||||
}
|
||||
|
||||
impl Drop for SurfmanRenderingContext {
|
||||
fn drop(&mut self) {
|
||||
let device = &mut self.device.borrow_mut();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
let _ = device.destroy_context(context);
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfmanRenderingContext {
|
||||
fn new(connection: &Connection, adapter: &Adapter) -> Result<Self, Error> {
|
||||
let mut device = connection.create_device(adapter)?;
|
||||
|
||||
let flags = ContextAttributeFlags::ALPHA |
|
||||
ContextAttributeFlags::DEPTH |
|
||||
ContextAttributeFlags::STENCIL;
|
||||
let gl_api = connection.gl_api();
|
||||
let version = match &gl_api {
|
||||
GLApi::GLES => surfman::GLVersion { major: 3, minor: 0 },
|
||||
GLApi::GL => surfman::GLVersion { major: 3, minor: 2 },
|
||||
};
|
||||
let context_descriptor =
|
||||
device.create_context_descriptor(&ContextAttributes { flags, version })?;
|
||||
let context = device.create_context(&context_descriptor, None)?;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let gleam_gl = {
|
||||
match gl_api {
|
||||
GLApi::GL => unsafe {
|
||||
gl::GlFns::load_with(|func_name| device.get_proc_address(&context, func_name))
|
||||
},
|
||||
GLApi::GLES => unsafe {
|
||||
gl::GlesFns::load_with(|func_name| device.get_proc_address(&context, func_name))
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let glow_gl = unsafe {
|
||||
glow::Context::from_loader_function(|function_name| {
|
||||
device.get_proc_address(&context, function_name)
|
||||
})
|
||||
};
|
||||
|
||||
Ok(SurfmanRenderingContext {
|
||||
gleam_gl,
|
||||
glow_gl: Arc::new(glow_gl),
|
||||
device: RefCell::new(device),
|
||||
context: RefCell::new(context),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_surface(&self, surface_type: SurfaceType<NativeWidget>) -> Result<Surface, Error> {
|
||||
let device = &mut self.device.borrow_mut();
|
||||
let context = &self.context.borrow();
|
||||
device.create_surface(context, SurfaceAccess::GPUOnly, surface_type)
|
||||
}
|
||||
|
||||
fn bind_surface(&self, surface: Surface) -> Result<(), Error> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
device
|
||||
.bind_surface_to_context(context, surface)
|
||||
.map_err(|(err, mut surface)| {
|
||||
let _ = device.destroy_surface(context, &mut surface);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_attached_swap_chain(&self) -> Result<SwapChain<Device>, Error> {
|
||||
let device = &mut self.device.borrow_mut();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
SwapChain::create_attached(device, context, SurfaceAccess::GPUOnly)
|
||||
}
|
||||
|
||||
fn resize_surface(&self, size: PhysicalSize<u32>) -> Result<(), Error> {
|
||||
let size = Size2D::new(size.width as i32, size.height as i32);
|
||||
let device = &mut self.device.borrow_mut();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
|
||||
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
|
||||
device.resize_surface(context, &mut surface, size)?;
|
||||
device
|
||||
.bind_surface_to_context(context, surface)
|
||||
.map_err(|(err, mut surface)| {
|
||||
let _ = device.destroy_surface(context, &mut surface);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
fn present_bound_surface(&self) -> Result<(), Error> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
|
||||
let mut surface = device.unbind_surface_from_context(context)?.unwrap();
|
||||
device.present_surface(context, &mut surface)?;
|
||||
device
|
||||
.bind_surface_to_context(context, surface)
|
||||
.map_err(|(err, mut surface)| {
|
||||
let _ = device.destroy_surface(context, &mut surface);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_context(&self) -> NativeContext {
|
||||
let device = &self.device.borrow();
|
||||
let context = &self.context.borrow();
|
||||
device.native_context(context)
|
||||
}
|
||||
|
||||
fn framebuffer(&self) -> Option<NativeFramebuffer> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &self.context.borrow();
|
||||
device
|
||||
.context_surface_info(context)
|
||||
.unwrap_or(None)
|
||||
.and_then(|info| info.framebuffer_object)
|
||||
}
|
||||
|
||||
fn prepare_for_rendering(&self) {
|
||||
let framebuffer_id = self
|
||||
.framebuffer()
|
||||
.map_or(0, |framebuffer| framebuffer.0.into());
|
||||
self.gleam_gl
|
||||
.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_id);
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
|
||||
let framebuffer_id = self
|
||||
.framebuffer()
|
||||
.map_or(0, |framebuffer| framebuffer.0.into());
|
||||
Framebuffer::read_framebuffer_to_image(&self.gleam_gl, framebuffer_id, source_rectangle)
|
||||
}
|
||||
|
||||
fn make_current(&self) -> Result<(), Error> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &mut self.context.borrow();
|
||||
device.make_context_current(context)
|
||||
}
|
||||
|
||||
fn create_texture(
|
||||
&self,
|
||||
surface: Surface,
|
||||
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
let SurfaceInfo {
|
||||
id: front_buffer_id,
|
||||
size,
|
||||
..
|
||||
} = 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)
|
||||
.map(|tex| tex.0.get())
|
||||
.unwrap_or(0);
|
||||
Some((surface_texture, gl_texture, size))
|
||||
}
|
||||
|
||||
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
|
||||
let device = &self.device.borrow();
|
||||
let context = &mut self.context.borrow_mut();
|
||||
device
|
||||
.destroy_surface_texture(context, surface_texture)
|
||||
.map_err(|(error, _)| error)
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn connection(&self) -> Option<Connection> {
|
||||
Some(self.device.borrow().connection())
|
||||
}
|
||||
}
|
||||
|
||||
/// A software rendering context that uses a software OpenGL implementation to render
|
||||
/// Servo. This will generally have bad performance, but can be used in situations where
|
||||
/// it is more convenient to have consistent, but slower display output.
|
||||
///
|
||||
/// The results of the render can be accessed via [`RenderingContext::read_to_image`].
|
||||
pub struct SoftwareRenderingContext {
|
||||
size: Cell<PhysicalSize<u32>>,
|
||||
surfman_rendering_info: SurfmanRenderingContext,
|
||||
swap_chain: SwapChain<Device>,
|
||||
}
|
||||
|
||||
impl SoftwareRenderingContext {
|
||||
pub fn new(size: PhysicalSize<u32>) -> Result<Self, Error> {
|
||||
let connection = Connection::new()?;
|
||||
let adapter = connection.create_software_adapter()?;
|
||||
let surfman_rendering_info = SurfmanRenderingContext::new(&connection, &adapter)?;
|
||||
|
||||
let surfman_size = Size2D::new(size.width as i32, size.height as i32);
|
||||
let surface =
|
||||
surfman_rendering_info.create_surface(SurfaceType::Generic { size: surfman_size })?;
|
||||
surfman_rendering_info.bind_surface(surface)?;
|
||||
surfman_rendering_info.make_current()?;
|
||||
|
||||
let swap_chain = surfman_rendering_info.create_attached_swap_chain()?;
|
||||
Ok(SoftwareRenderingContext {
|
||||
size: Cell::new(size),
|
||||
surfman_rendering_info,
|
||||
swap_chain,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SoftwareRenderingContext {
|
||||
fn drop(&mut self) {
|
||||
let device = &mut self.surfman_rendering_info.device.borrow_mut();
|
||||
let context = &mut self.surfman_rendering_info.context.borrow_mut();
|
||||
let _ = self.swap_chain.destroy(device, context);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderingContext for SoftwareRenderingContext {
|
||||
fn prepare_for_rendering(&self) {
|
||||
self.surfman_rendering_info.prepare_for_rendering();
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
|
||||
self.surfman_rendering_info.read_to_image(source_rectangle)
|
||||
}
|
||||
|
||||
fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.get()
|
||||
}
|
||||
|
||||
fn resize(&self, size: PhysicalSize<u32>) {
|
||||
if self.size.get() == size {
|
||||
return;
|
||||
}
|
||||
|
||||
self.size.set(size);
|
||||
|
||||
let device = &mut self.surfman_rendering_info.device.borrow_mut();
|
||||
let context = &mut self.surfman_rendering_info.context.borrow_mut();
|
||||
let size = Size2D::new(size.width as i32, size.height as i32);
|
||||
let _ = self.swap_chain.resize(device, context, size);
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
let device = &mut self.surfman_rendering_info.device.borrow_mut();
|
||||
let context = &mut self.surfman_rendering_info.context.borrow_mut();
|
||||
let _ = self
|
||||
.swap_chain
|
||||
.swap_buffers(device, context, PreserveBuffer::No);
|
||||
}
|
||||
|
||||
fn make_current(&self) -> Result<(), Error> {
|
||||
self.surfman_rendering_info.make_current()
|
||||
}
|
||||
|
||||
fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
self.surfman_rendering_info.gleam_gl.clone()
|
||||
}
|
||||
|
||||
fn glow_gl_api(&self) -> Arc<glow::Context> {
|
||||
self.surfman_rendering_info.glow_gl.clone()
|
||||
}
|
||||
|
||||
fn create_texture(
|
||||
&self,
|
||||
surface: Surface,
|
||||
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
|
||||
self.surfman_rendering_info.create_texture(surface)
|
||||
}
|
||||
|
||||
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
|
||||
self.surfman_rendering_info.destroy_texture(surface_texture)
|
||||
}
|
||||
|
||||
fn connection(&self) -> Option<Connection> {
|
||||
self.surfman_rendering_info.connection()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`RenderingContext`] that uses the `surfman` library to render to a
|
||||
/// `raw-window-handle` identified window. `surfman` will attempt to create an
|
||||
/// OpenGL context and surface for this window. This is a simple implementation
|
||||
/// of the [`RenderingContext`] crate, but by default it paints to the entire window
|
||||
/// surface.
|
||||
///
|
||||
/// If you would like to paint to only a portion of the window, consider using
|
||||
/// [`OffscreenRenderingContext`] by calling [`WindowRenderingContext::offscreen_context`].
|
||||
pub struct WindowRenderingContext {
|
||||
size: Cell<PhysicalSize<u32>>,
|
||||
surfman_context: SurfmanRenderingContext,
|
||||
}
|
||||
|
||||
impl WindowRenderingContext {
|
||||
pub fn new(
|
||||
display_handle: DisplayHandle,
|
||||
window_handle: WindowHandle,
|
||||
size: PhysicalSize<u32>,
|
||||
) -> Result<Self, Error> {
|
||||
let connection = Connection::from_display_handle(display_handle)?;
|
||||
let adapter = connection.create_adapter()?;
|
||||
let surfman_context = SurfmanRenderingContext::new(&connection, &adapter)?;
|
||||
|
||||
let native_widget = connection
|
||||
.create_native_widget_from_window_handle(
|
||||
window_handle,
|
||||
Size2D::new(size.width as i32, size.height as i32),
|
||||
)
|
||||
.expect("Failed to create native widget");
|
||||
|
||||
let surface = surfman_context.create_surface(SurfaceType::Widget { native_widget })?;
|
||||
surfman_context.bind_surface(surface)?;
|
||||
surfman_context.make_current()?;
|
||||
|
||||
Ok(Self {
|
||||
size: Cell::new(size),
|
||||
surfman_context,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn offscreen_context(
|
||||
self: &Rc<Self>,
|
||||
size: PhysicalSize<u32>,
|
||||
) -> OffscreenRenderingContext {
|
||||
OffscreenRenderingContext::new(self.clone(), size)
|
||||
}
|
||||
|
||||
/// Stop rendering to the window that was used to create this `WindowRenderingContext`
|
||||
/// or last set with [`Self::set_window`].
|
||||
///
|
||||
/// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s.
|
||||
pub fn take_window(&self) -> Result<(), Error> {
|
||||
let device = self.surfman_context.device.borrow_mut();
|
||||
let mut context = self.surfman_context.context.borrow_mut();
|
||||
let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap();
|
||||
device.destroy_surface(&mut context, &mut surface)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replace the window that this [`WindowRenderingContext`] renders to and give it a new
|
||||
/// size.
|
||||
///
|
||||
/// TODO: This should be removed once `WebView`s can replace their `RenderingContext`s.
|
||||
pub fn set_window(
|
||||
&self,
|
||||
window_handle: WindowHandle,
|
||||
size: PhysicalSize<u32>,
|
||||
) -> Result<(), Error> {
|
||||
let mut device = self.surfman_context.device.borrow_mut();
|
||||
let mut context = self.surfman_context.context.borrow_mut();
|
||||
|
||||
let native_widget = device
|
||||
.connection()
|
||||
.create_native_widget_from_window_handle(
|
||||
window_handle,
|
||||
Size2D::new(size.width as i32, size.height as i32),
|
||||
)
|
||||
.expect("Failed to create native widget");
|
||||
|
||||
let surface_access = SurfaceAccess::GPUOnly;
|
||||
let surface_type = SurfaceType::Widget { native_widget };
|
||||
let surface = device.create_surface(&context, surface_access, surface_type)?;
|
||||
|
||||
device
|
||||
.bind_surface_to_context(&mut context, surface)
|
||||
.map_err(|(err, mut surface)| {
|
||||
let _ = device.destroy_surface(&mut context, &mut surface);
|
||||
err
|
||||
})?;
|
||||
device.make_context_current(&context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn surfman_details(&self) -> (RefMut<Device>, RefMut<Context>) {
|
||||
(
|
||||
self.surfman_context.device.borrow_mut(),
|
||||
self.surfman_context.context.borrow_mut(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderingContext for WindowRenderingContext {
|
||||
fn prepare_for_rendering(&self) {
|
||||
self.surfman_context.prepare_for_rendering();
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
|
||||
self.surfman_context.read_to_image(source_rectangle)
|
||||
}
|
||||
|
||||
fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size.get()
|
||||
}
|
||||
|
||||
fn resize(&self, size: PhysicalSize<u32>) {
|
||||
match self.surfman_context.resize_surface(size) {
|
||||
Ok(..) => self.size.set(size),
|
||||
Err(error) => warn!("Error resizing surface: {error:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
if let Err(error) = self.surfman_context.present_bound_surface() {
|
||||
warn!("Error presenting surface: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn make_current(&self) -> Result<(), Error> {
|
||||
self.surfman_context.make_current()
|
||||
}
|
||||
|
||||
fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
self.surfman_context.gleam_gl.clone()
|
||||
}
|
||||
|
||||
fn glow_gl_api(&self) -> Arc<glow::Context> {
|
||||
self.surfman_context.glow_gl.clone()
|
||||
}
|
||||
|
||||
fn create_texture(
|
||||
&self,
|
||||
surface: Surface,
|
||||
) -> Option<(SurfaceTexture, u32, UntypedSize2D<i32>)> {
|
||||
self.surfman_context.create_texture(surface)
|
||||
}
|
||||
|
||||
fn destroy_texture(&self, surface_texture: SurfaceTexture) -> Option<Surface> {
|
||||
self.surfman_context.destroy_texture(surface_texture)
|
||||
}
|
||||
|
||||
fn connection(&self) -> Option<Connection> {
|
||||
self.surfman_context.connection()
|
||||
}
|
||||
}
|
||||
|
||||
struct Framebuffer {
|
||||
gl: Rc<dyn Gl>,
|
||||
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: PhysicalSize<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,
|
||||
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: DeviceIntRect) -> Option<RgbaImage> {
|
||||
Self::read_framebuffer_to_image(&self.gl, self.framebuffer_id, source_rectangle)
|
||||
}
|
||||
|
||||
fn read_framebuffer_to_image(
|
||||
gl: &Rc<dyn Gl>,
|
||||
framebuffer_id: u32,
|
||||
source_rectangle: DeviceIntRect,
|
||||
) -> 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.min.x,
|
||||
source_rectangle.min.y,
|
||||
source_rectangle.width(),
|
||||
source_rectangle.height(),
|
||||
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: Rc<WindowRenderingContext>,
|
||||
size: Cell<PhysicalSize<u32>>,
|
||||
framebuffer: RefCell<Framebuffer>,
|
||||
}
|
||||
|
||||
type RenderToParentCallback = Box<dyn Fn(&glow::Context, Rect<i32>) + Send + Sync>;
|
||||
|
||||
impl OffscreenRenderingContext {
|
||||
fn new(parent_context: Rc<WindowRenderingContext>, size: PhysicalSize<u32>) -> Self {
|
||||
let framebuffer = RefCell::new(Framebuffer::new(parent_context.gleam_gl_api(), size));
|
||||
Self {
|
||||
parent_context,
|
||||
size: Cell::new(size),
|
||||
framebuffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_context(&self) -> &WindowRenderingContext {
|
||||
&self.parent_context
|
||||
}
|
||||
|
||||
pub fn render_to_parent_callback(&self) -> Option<RenderToParentCallback> {
|
||||
// Don't accept a `None` context for the source framebuffer.
|
||||
let front_framebuffer_id =
|
||||
NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer)?;
|
||||
let parent_context_framebuffer_id = self.parent_context.surfman_context.framebuffer();
|
||||
let size = self.size.get();
|
||||
let size = Size2D::new(size.width as i32, size.height as i32);
|
||||
Some(Box::new(move |gl, target_rect| {
|
||||
Self::blit_framebuffer(
|
||||
gl,
|
||||
Rect::new(Point2D::origin(), size.to_i32()),
|
||||
front_framebuffer_id,
|
||||
target_rect,
|
||||
parent_context_framebuffer_id,
|
||||
);
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn blit_framebuffer(
|
||||
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 size(&self) -> PhysicalSize<u32> {
|
||||
self.size.get()
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: PhysicalSize<u32>) {
|
||||
let old_size = self.size.get();
|
||||
if old_size == new_size {
|
||||
return;
|
||||
}
|
||||
|
||||
let gl = self.parent_context.gleam_gl_api();
|
||||
let new_framebuffer = Framebuffer::new(gl.clone(), new_size);
|
||||
|
||||
let old_framebuffer =
|
||||
std::mem::replace(&mut *self.framebuffer.borrow_mut(), new_framebuffer);
|
||||
self.size.set(new_size);
|
||||
|
||||
let blit_size = new_size.min(old_size);
|
||||
let rect = Rect::new(
|
||||
Point2D::origin(),
|
||||
Size2D::new(blit_size.width, blit_size.height),
|
||||
)
|
||||
.to_i32();
|
||||
|
||||
let Some(old_framebuffer_id) =
|
||||
NonZeroU32::new(old_framebuffer.framebuffer_id).map(NativeFramebuffer)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let new_framebuffer_id =
|
||||
NonZeroU32::new(self.framebuffer.borrow().framebuffer_id).map(NativeFramebuffer);
|
||||
Self::blit_framebuffer(
|
||||
&self.glow_gl_api(),
|
||||
rect,
|
||||
old_framebuffer_id,
|
||||
rect,
|
||||
new_framebuffer_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_for_rendering(&self) {
|
||||
self.framebuffer.borrow().bind();
|
||||
}
|
||||
|
||||
fn present(&self) {}
|
||||
|
||||
fn make_current(&self) -> Result<(), surfman::Error> {
|
||||
self.parent_context.make_current()
|
||||
}
|
||||
|
||||
fn gleam_gl_api(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
self.parent_context.gleam_gl_api()
|
||||
}
|
||||
|
||||
fn glow_gl_api(&self) -> Arc<glow::Context> {
|
||||
self.parent_context.glow_gl_api()
|
||||
}
|
||||
|
||||
fn create_texture(
|
||||
&self,
|
||||
surface: Surface,
|
||||
) -> Option<(SurfaceTexture, u32, UntypedSize2D<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> {
|
||||
self.parent_context.connection()
|
||||
}
|
||||
|
||||
fn read_to_image(&self, source_rectangle: DeviceIntRect) -> Option<RgbaImage> {
|
||||
self.framebuffer.borrow().read_to_image(source_rectangle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use dpi::PhysicalSize;
|
||||
use euclid::{Box2D, Point2D, 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, PhysicalSize::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 rect = Box2D::from_origin_and_size(Point2D::zero(), Size2D::new(SIZE, SIZE));
|
||||
let img = framebuffer
|
||||
.read_to_image(rect.to_i32())
|
||||
.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(())
|
||||
}
|
||||
}
|
188
components/shared/compositing/tests/compositor.rs
Normal file
188
components/shared/compositing/tests/compositor.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* 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 base::id::ScrollTreeNodeId;
|
||||
use compositing_traits::display_list::{
|
||||
AxesScrollSensitivity, ScrollSensitivity, ScrollTree, ScrollableNodeInfo,
|
||||
};
|
||||
use euclid::Size2D;
|
||||
use webrender_api::units::LayoutVector2D;
|
||||
use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, SpatialId};
|
||||
|
||||
fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let num_nodes = tree.nodes.len();
|
||||
let parent = if num_nodes > 0 {
|
||||
Some(ScrollTreeNodeId {
|
||||
index: num_nodes - 1,
|
||||
spatial_id: SpatialId::new(num_nodes - 1, pipeline_id),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
tree.add_scroll_tree_node(
|
||||
parent.as_ref(),
|
||||
SpatialId::new(num_nodes, pipeline_id),
|
||||
Some(ScrollableNodeInfo {
|
||||
external_id: ExternalScrollId(num_nodes as u64, pipeline_id),
|
||||
scrollable_size: Size2D::new(100.0, 100.0),
|
||||
scroll_sensitivity: AxesScrollSensitivity {
|
||||
x: ScrollSensitivity::ScriptAndInputEvents,
|
||||
y: ScrollSensitivity::ScriptAndInputEvents,
|
||||
},
|
||||
offset: LayoutVector2D::zero(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scroll_tree_simple_scroll() {
|
||||
let mut scroll_tree = ScrollTree::default();
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let id = add_mock_scroll_node(&mut scroll_tree);
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(
|
||||
&id,
|
||||
ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(-20.0, -40.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset));
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(&id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)))
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(0.0, 0.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset));
|
||||
|
||||
// Scroll offsets must be negative.
|
||||
let result = scroll_tree
|
||||
.scroll_node_or_ancestor(&id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0)));
|
||||
assert!(result.is_none());
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&id).offset(),
|
||||
Some(LayoutVector2D::new(0.0, 0.0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scroll_tree_simple_scroll_chaining() {
|
||||
let mut scroll_tree = ScrollTree::default();
|
||||
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let parent_id = add_mock_scroll_node(&mut scroll_tree);
|
||||
let unscrollable_child_id =
|
||||
scroll_tree.add_scroll_tree_node(Some(&parent_id), SpatialId::new(1, pipeline_id), None);
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(
|
||||
&unscrollable_child_id,
|
||||
ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(-20.0, -40.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&parent_id).offset(),
|
||||
Some(expected_offset)
|
||||
);
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(
|
||||
&unscrollable_child_id,
|
||||
ScrollLocation::Delta(LayoutVector2D::new(-10.0, -15.0)),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(-30.0, -55.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&parent_id).offset(),
|
||||
Some(expected_offset)
|
||||
);
|
||||
assert_eq!(scroll_tree.get_node(&unscrollable_child_id).offset(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scroll_tree_chain_when_at_extent() {
|
||||
let mut scroll_tree = ScrollTree::default();
|
||||
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let parent_id = add_mock_scroll_node(&mut scroll_tree);
|
||||
let child_id = add_mock_scroll_node(&mut scroll_tree);
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(&child_id, ScrollLocation::End)
|
||||
.unwrap();
|
||||
|
||||
let expected_offset = LayoutVector2D::new(0.0, -100.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(1, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&child_id).offset(),
|
||||
Some(expected_offset)
|
||||
);
|
||||
|
||||
// The parent will have scrolled because the child is already at the extent
|
||||
// of its scroll area in the y axis.
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(
|
||||
&child_id,
|
||||
ScrollLocation::Delta(LayoutVector2D::new(0.0, -10.0)),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(0.0, -10.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&parent_id).offset(),
|
||||
Some(expected_offset)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scroll_tree_chain_through_overflow_hidden() {
|
||||
let mut scroll_tree = ScrollTree::default();
|
||||
|
||||
// Create a tree with a scrollable leaf, but make its `scroll_sensitivity`
|
||||
// reflect `overflow: hidden` ie not responsive to non-script scroll events.
|
||||
let pipeline_id = PipelineId(0, 0);
|
||||
let parent_id = add_mock_scroll_node(&mut scroll_tree);
|
||||
let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree);
|
||||
scroll_tree
|
||||
.get_node_mut(&overflow_hidden_id)
|
||||
.scroll_info
|
||||
.as_mut()
|
||||
.map(|info| {
|
||||
info.scroll_sensitivity = AxesScrollSensitivity {
|
||||
x: ScrollSensitivity::Script,
|
||||
y: ScrollSensitivity::Script,
|
||||
};
|
||||
});
|
||||
|
||||
let (scrolled_id, offset) = scroll_tree
|
||||
.scroll_node_or_ancestor(
|
||||
&overflow_hidden_id,
|
||||
ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_offset = LayoutVector2D::new(-20.0, -40.0);
|
||||
assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id));
|
||||
assert_eq!(offset, expected_offset);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&parent_id).offset(),
|
||||
Some(expected_offset)
|
||||
);
|
||||
assert_eq!(
|
||||
scroll_tree.get_node(&overflow_hidden_id).offset(),
|
||||
Some(LayoutVector2D::new(0.0, 0.0))
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue