aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/webrender/lib.rs
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2024-10-09 10:30:24 -0700
committerGitHub <noreply@github.com>2024-10-09 17:30:24 +0000
commit9195344b75b4e5fd1cd86671b10df46a956f0fea (patch)
tree20ef541e886032195be6ae57403e4c05ff438c26 /components/shared/webrender/lib.rs
parent30cbf01280dc3aa03caeaba11f55808f7ba59cf3 (diff)
downloadservo-9195344b75b4e5fd1cd86671b10df46a956f0fea.tar.gz
servo-9195344b75b4e5fd1cd86671b10df46a956f0fea.zip
compositor: Create a single cross-process compositor API (#33619) (#33660)
Instead of exposing many different kinds of messages to the compositor that are routed through the constellation, expose a single message type which can be sent across IPC channels. In addition, this IPC channel and the route to the crossbeam channel with the compositor is created along with the `CompositorProxy`, simplifying what needs to be passed around during pipeline initialization. Previously, some image updates (from video) were sent over IPC with a special serialization routine and some were sent via crossbeam channels (canvas). Now all updates go over the IPC channel `IpcSharedMemory` is used to avoid serialization penalties. This should improve performance and reduce copies for video, but add a memory copy overhead for canvas. This will improve in the future when canvas renders directly into a texture. All-in-all this is a simplification which opens the path toward having a standard compositor API and reduces the number of duplicate messages and proxying that had to happen in libservo. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/shared/webrender/lib.rs')
-rw-r--r--components/shared/webrender/lib.rs546
1 files changed, 266 insertions, 280 deletions
diff --git a/components/shared/webrender/lib.rs b/components/shared/webrender/lib.rs
index 3e51e6bc6ea..807c8909d61 100644
--- a/components/shared/webrender/lib.rs
+++ b/components/shared/webrender/lib.rs
@@ -7,11 +7,11 @@
pub mod display_list;
pub mod rendering_context;
+use core::fmt;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use base::id::PipelineId;
-use crossbeam_channel::Sender;
use display_list::{CompositorDisplayListInfo, ScrollTreeNodeId};
use embedder_traits::Cursor;
use euclid::default::Size2D;
@@ -19,7 +19,7 @@ use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
use libc::c_void;
use log::warn;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect};
+use webrender_api::units::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutPoint, TexelRect};
use webrender_api::{
BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData,
ExternalImageHandler, ExternalImageId, ExternalImageSource, ExternalScrollId,
@@ -29,6 +29,261 @@ use webrender_api::{
pub use crate::rendering_context::RenderingContext;
+#[derive(Deserialize, Serialize)]
+pub enum CrossProcessCompositorMessage {
+ /// Inform WebRender of the existence of this pipeline.
+ SendInitialTransaction(WebRenderPipelineId),
+ /// Perform a scroll operation.
+ SendScrollNode(WebRenderPipelineId, LayoutPoint, ExternalScrollId),
+ /// Inform WebRender of a new display list for the given pipeline.
+ SendDisplayList {
+ /// 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(IpcSender<DeviceIntRect>),
+ /// Get the size of the screen that the client window inhabits.
+ GetScreenSize(IpcSender<DeviceIntSize>),
+ /// Get the available screen size (without toolbars and docks) for the screen
+ /// the client window inhabits.
+ GetAvailableScreenSize(IpcSender<DeviceIntSize>),
+}
+
+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,
+ pipeline_id: WebRenderPipelineId,
+ point: LayoutPoint,
+ scroll_id: ExternalScrollId,
+ ) {
+ if let Err(e) = self.0.send(CrossProcessCompositorMessage::SendScrollNode(
+ 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,
+ 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 {
+ 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.
@@ -183,303 +438,34 @@ impl ExternalImageHandler for WebrenderExternalImageHandlers {
}
}
-pub trait WebRenderFontApi {
- fn add_font_instance(
- &self,
- font_instance_key: FontInstanceKey,
- font_key: FontKey,
- size: f32,
- flags: FontInstanceFlags,
- );
- fn add_font(&self, font_key: FontKey, data: Arc<IpcSharedMemory>, index: u32);
- fn add_system_font(&self, font_key: FontKey, handle: NativeFontHandle);
- fn fetch_font_keys(
- &self,
- number_of_font_keys: usize,
- number_of_font_instance_keys: usize,
- ) -> (Vec<FontKey>, Vec<FontInstanceKey>);
-}
-
-pub enum CanvasToCompositorMsg {
- GenerateKey(Sender<ImageKey>),
- UpdateImages(Vec<ImageUpdate>),
-}
-
-pub enum FontToCompositorMsg {
- GenerateKeys(usize, usize, Sender<(Vec<FontKey>, Vec<FontInstanceKey>)>),
- AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
- AddFont(FontKey, u32, Arc<IpcSharedMemory>),
- AddSystemFont(FontKey, NativeFontHandle),
-}
-
-#[derive(Deserialize, Serialize)]
-pub enum NetToCompositorMsg {
- AddImage(ImageKey, ImageDescriptor, ImageData),
- GenerateImageKey(IpcSender<ImageKey>),
-}
-
-/// The set of WebRender operations that can be initiated by the content process.
-#[derive(Deserialize, Serialize)]
-pub enum ScriptToCompositorMsg {
- /// Inform WebRender of the existence of this pipeline.
- SendInitialTransaction(WebRenderPipelineId),
- /// Perform a scroll operation.
- SendScrollNode(WebRenderPipelineId, LayoutPoint, ExternalScrollId),
- /// Inform WebRender of a new display list for the given pipeline.
- SendDisplayList {
- /// 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>),
- /// Perform a resource update operation.
- UpdateImages(Vec<SerializedImageUpdate>),
- /// Remove the given font resources from our WebRender instance.
- RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
- AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
- AddFont(FontKey, Arc<IpcSharedMemory>, u32),
-}
-
-/// A mechanism to send messages from networking to the WebRender instance.
-#[derive(Clone, Deserialize, Serialize)]
-pub struct WebRenderNetApi(IpcSender<NetToCompositorMsg>);
-
-impl WebRenderNetApi {
- pub fn new(sender: IpcSender<NetToCompositorMsg>) -> Self {
- Self(sender)
- }
-
- pub fn generate_image_key(&self) -> ImageKey {
- let (sender, receiver) = ipc::channel().unwrap();
- self.0
- .send(NetToCompositorMsg::GenerateImageKey(sender))
- .expect("error sending image key generation");
- receiver.recv().expect("error receiving image key result")
- }
-
- pub fn add_image(&self, key: ImageKey, descriptor: ImageDescriptor, data: ImageData) {
- if let Err(e) = self
- .0
- .send(NetToCompositorMsg::AddImage(key, descriptor, data))
- {
- warn!("Error sending image update: {}", e);
- }
- }
-}
-
-/// A mechanism to send messages from ScriptThread to the parent process' WebRender instance.
-#[derive(Clone, Deserialize, Serialize)]
-pub struct WebRenderScriptApi(IpcSender<ScriptToCompositorMsg>);
-
-impl WebRenderScriptApi {
- /// Create a new [`WebRenderScriptApi`] object that wraps the provided channel sender.
- pub fn new(sender: IpcSender<ScriptToCompositorMsg>) -> Self {
- Self(sender)
- }
-
- /// Create a new [`WebRenderScriptApi`] object that does not have a listener on the
- /// other end.
- pub fn dummy() -> Self {
- let (sender, _) = ipc::channel().unwrap();
- Self::new(sender)
- }
-
- /// Get the sender for this proxy.
- pub fn sender(&self) -> &IpcSender<ScriptToCompositorMsg> {
- &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(ScriptToCompositorMsg::SendInitialTransaction(pipeline))
- {
- warn!("Error sending initial transaction: {}", e);
- }
- }
-
- /// Perform a scroll operation.
- pub fn send_scroll_node(
- &self,
- pipeline_id: WebRenderPipelineId,
- point: LayoutPoint,
- scroll_id: ExternalScrollId,
- ) {
- if let Err(e) = self.0.send(ScriptToCompositorMsg::SendScrollNode(
- 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,
- 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(ScriptToCompositorMsg::SendDisplayList {
- 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(ScriptToCompositorMsg::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(ScriptToCompositorMsg::GenerateImageKey(sender))
- .ok()?;
- receiver.recv().ok()
- }
-
- /// Perform a resource update operation.
- pub fn update_images(&self, updates: Vec<ImageUpdate>) {
- let mut senders = Vec::new();
- // Convert `ImageUpdate` to `SerializedImageUpdate` because `ImageData` may contain large
- // byes. With this conversion, we send `IpcBytesReceiver` instead and use it to send the
- // actual bytes.
- let updates = updates
- .into_iter()
- .map(|update| match update {
- ImageUpdate::AddImage(k, d, data) => {
- let data = match data {
- ImageData::Raw(r) => {
- let (sender, receiver) = ipc::bytes_channel().unwrap();
- senders.push((sender, r));
- SerializedImageData::Raw(receiver)
- },
- ImageData::External(e) => SerializedImageData::External(e),
- };
- SerializedImageUpdate::AddImage(k, d, data)
- },
- ImageUpdate::DeleteImage(k) => SerializedImageUpdate::DeleteImage(k),
- ImageUpdate::UpdateImage(k, d, data) => {
- let data = match data {
- ImageData::Raw(r) => {
- let (sender, receiver) = ipc::bytes_channel().unwrap();
- senders.push((sender, r));
- SerializedImageData::Raw(receiver)
- },
- ImageData::External(e) => SerializedImageData::External(e),
- };
- SerializedImageUpdate::UpdateImage(k, d, data)
- },
- })
- .collect();
-
- if let Err(e) = self.0.send(ScriptToCompositorMsg::UpdateImages(updates)) {
- warn!("error sending image updates: {}", e);
- }
-
- senders.into_iter().for_each(|(tx, data)| {
- if let Err(e) = tx.send(&data) {
- warn!("error sending image data: {}", 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(ScriptToCompositorMsg::RemoveFonts(keys, instance_keys));
- }
-}
-
#[derive(Deserialize, Serialize)]
/// Serializable image updates that must be performed by WebRender.
pub enum ImageUpdate {
/// Register a new image.
- AddImage(ImageKey, ImageDescriptor, ImageData),
+ AddImage(ImageKey, ImageDescriptor, SerializableImageData),
/// Delete a previously registered image registration.
DeleteImage(ImageKey),
/// Update an existing image registration.
- UpdateImage(ImageKey, ImageDescriptor, ImageData),
-}
-
-#[derive(Deserialize, Serialize)]
-/// Serialized `ImageUpdate`.
-pub enum SerializedImageUpdate {
- /// Register a new image.
- AddImage(ImageKey, ImageDescriptor, SerializedImageData),
- /// Delete a previously registered image registration.
- DeleteImage(ImageKey),
- /// Update an existing image registration.
- UpdateImage(ImageKey, ImageDescriptor, SerializedImageData),
+ 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 SerializedImageData {
+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(ipc::IpcBytesReceiver),
+ 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 SerializedImageData {
- /// Convert to ``ImageData`.
- pub fn to_image_data(&self) -> Result<ImageData, ipc::IpcError> {
- match self {
- SerializedImageData::Raw(rx) => rx.recv().map(ImageData::new),
- SerializedImageData::External(image) => Ok(ImageData::External(*image)),
+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),
}
}
}