canvas: Update the image as part of update the rendering (#35996)

* Create `update_rendering` in `CanvasState` instead of manually updating in layout

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Mark as dirty and do flushes

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* fixup rebase

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Update components/script/dom/htmlcanvaselement.rs

Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
sagudev 2025-03-25 07:38:02 +01:00 committed by GitHub
parent ed995e61a6
commit 62737b3830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 72 additions and 51 deletions

View file

@ -79,12 +79,6 @@ impl<'a> CanvasPaintThread<'a> {
canvas_paint_thread.canvas(canvas_id).send_pixels(chan); canvas_paint_thread.canvas(canvas_id).send_pixels(chan);
}, },
}, },
Ok(CanvasMsg::FromLayout(message, canvas_id)) => match message {
FromLayoutMsg::UpdateImage(sender) => {
canvas_paint_thread.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap();
},
},
Err(e) => { Err(e) => {
warn!("Error on CanvasPaintThread receive ({})", e); warn!("Error on CanvasPaintThread receive ({})", e);
}, },
@ -256,6 +250,10 @@ impl<'a> CanvasPaintThread<'a> {
Canvas2dMsg::SetTextBaseline(text_baseline) => { Canvas2dMsg::SetTextBaseline(text_baseline) => {
self.canvas(canvas_id).set_text_baseline(text_baseline) self.canvas(canvas_id).set_text_baseline(text_baseline)
}, },
Canvas2dMsg::UpdateImage(sender) => {
self.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap();
},
} }
} }

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
@ -155,9 +155,7 @@ where
let canvas_data = node.canvas_data()?; let canvas_data = node.canvas_data()?;
let source = match canvas_data.source { let source = match canvas_data.source {
HTMLCanvasDataSource::WebGL(texture_id) => CanvasSource::WebGL(texture_id), HTMLCanvasDataSource::WebGL(texture_id) => CanvasSource::WebGL(texture_id),
HTMLCanvasDataSource::Image((image_key, canvas_id, ipc_sender)) => { HTMLCanvasDataSource::Image(image_key) => CanvasSource::Image(image_key),
CanvasSource::Image((image_key, canvas_id, Arc::new(Mutex::new(ipc_sender))))
},
HTMLCanvasDataSource::WebGPU(image_key) => CanvasSource::WebGPU(image_key), HTMLCanvasDataSource::WebGPU(image_key) => CanvasSource::WebGPU(image_key),
HTMLCanvasDataSource::Empty => CanvasSource::Empty, HTMLCanvasDataSource::Empty => CanvasSource::Empty,
}; };

View file

@ -4,14 +4,12 @@
use std::cell::LazyCell; use std::cell::LazyCell;
use std::fmt; use std::fmt;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use app_units::Au; use app_units::Au;
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
use canvas_traits::canvas::{CanvasId, CanvasMsg, FromLayoutMsg};
use data_url::DataUrl; use data_url::DataUrl;
use euclid::Size2D; use euclid::Size2D;
use ipc_channel::ipc::{self, IpcSender};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use pixels::Image; use pixels::Image;
use script_layout_interface::IFrameSize; use script_layout_interface::IFrameSize;
@ -98,7 +96,7 @@ impl NaturalSizes {
pub(crate) enum CanvasSource { pub(crate) enum CanvasSource {
WebGL(ImageKey), WebGL(ImageKey),
Image((ImageKey, CanvasId, Arc<Mutex<IpcSender<CanvasMsg>>>)), Image(ImageKey),
WebGPU(ImageKey), WebGPU(ImageKey),
/// transparent black /// transparent black
Empty, Empty,
@ -381,18 +379,7 @@ impl ReplacedContents {
let image_key = match canvas_info.source { let image_key = match canvas_info.source {
CanvasSource::WebGL(image_key) => image_key, CanvasSource::WebGL(image_key) => image_key,
CanvasSource::WebGPU(image_key) => image_key, CanvasSource::WebGPU(image_key) => image_key,
CanvasSource::Image((image_key, canvas_id, ref ipc_renderer)) => { CanvasSource::Image(image_key) => image_key,
let ipc_renderer = ipc_renderer.lock().unwrap();
let (sender, receiver) = ipc::channel().unwrap();
ipc_renderer
.send(CanvasMsg::FromLayout(
FromLayoutMsg::UpdateImage(sender),
canvas_id,
))
.unwrap();
receiver.recv().unwrap();
image_key
},
CanvasSource::Empty => return vec![], CanvasSource::Empty => return vec![],
}; };
vec![Fragment::Image(ArcRefCell::new(ImageFragment { vec![Fragment::Image(ArcRefCell::new(ImageFragment {

View file

@ -54,7 +54,7 @@ use crate::dom::element::{Element, cors_setting_for_element};
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::{CanvasContext, HTMLCanvasElement}; use crate::dom::htmlcanvaselement::{CanvasContext, HTMLCanvasElement};
use crate::dom::imagedata::ImageData; use crate::dom::imagedata::ImageData;
use crate::dom::node::{Node, NodeDamage, NodeTraits}; use crate::dom::node::{Node, NodeTraits};
use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext}; use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext};
use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
use crate::dom::textmetrics::TextMetrics; use crate::dom::textmetrics::TextMetrics;
@ -221,6 +221,18 @@ impl CanvasState {
.unwrap() .unwrap()
} }
/// Updates WR image and blocks on completion
pub(crate) fn update_rendering(&self) {
let (sender, receiver) = ipc::channel().unwrap();
self.ipc_renderer
.send(CanvasMsg::Canvas2d(
Canvas2dMsg::UpdateImage(sender),
self.canvas_id,
))
.unwrap();
receiver.recv().unwrap();
}
// https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) { pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
self.reset_to_initial_state(); self.reset_to_initial_state();
@ -608,7 +620,7 @@ impl CanvasState {
pub(crate) fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) { pub(crate) fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) {
if let Some(canvas) = canvas { if let Some(canvas) = canvas {
canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); canvas.mark_as_dirty();
} }
} }

View file

@ -7,6 +7,7 @@ use dom_struct::dom_struct;
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use ipc_channel::ipc::IpcSharedMemory; use ipc_channel::ipc::IpcSharedMemory;
use profile_traits::ipc; use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use script_layout_interface::HTMLCanvasDataSource; use script_layout_interface::HTMLCanvasDataSource;
use servo_url::ServoUrl; use servo_url::ServoUrl;
@ -30,6 +31,7 @@ use crate::dom::dommatrix::DOMMatrix;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::htmlcanvaselement::HTMLCanvasElement;
use crate::dom::imagedata::ImageData; use crate::dom::imagedata::ImageData;
use crate::dom::node::{Node, NodeDamage, NodeTraits};
use crate::dom::textmetrics::TextMetrics; use crate::dom::textmetrics::TextMetrics;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -116,11 +118,7 @@ impl CanvasRenderingContext2D {
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> { impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> {
fn canvas_data_source(self) -> HTMLCanvasDataSource { fn canvas_data_source(self) -> HTMLCanvasDataSource {
let canvas_state = &self.unsafe_get().canvas_state; let canvas_state = &self.unsafe_get().canvas_state;
HTMLCanvasDataSource::Image(( HTMLCanvasDataSource::Image(canvas_state.image_key())
canvas_state.image_key(),
canvas_state.get_canvas_id(),
canvas_state.get_ipc_renderer().clone(),
))
} }
} }
@ -135,6 +133,10 @@ impl CanvasContext for CanvasRenderingContext2D {
self.canvas.clone() self.canvas.clone()
} }
fn update_rendering(&self) {
self.canvas_state.update_rendering();
}
fn resize(&self) { fn resize(&self) {
self.set_bitmap_dimensions(self.size().cast()) self.set_bitmap_dimensions(self.size().cast())
} }
@ -156,7 +158,10 @@ impl CanvasContext for CanvasRenderingContext2D {
} }
fn mark_as_dirty(&self) { fn mark_as_dirty(&self) {
self.canvas_state.mark_as_dirty(self.canvas.canvas()) if let Some(canvas) = self.canvas.canvas() {
canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
canvas.owner_document().add_dirty_2d_canvas(self);
}
} }
} }

View file

@ -17,6 +17,7 @@ use std::time::{Duration, Instant};
use base::cross_process_instant::CrossProcessInstant; use base::cross_process_instant::CrossProcessInstant;
use base::id::WebViewId; use base::id::WebViewId;
use canvas_traits::canvas::CanvasId;
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg}; use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
use chrono::Local; use chrono::Local;
use constellation_traits::{AnimationTickType, CompositorHitTestResult}; use constellation_traits::{AnimationTickType, CompositorHitTestResult};
@ -74,6 +75,7 @@ use webgpu::swapchain::WebGPUContextId;
use webrender_api::units::DeviceIntRect; use webrender_api::units::DeviceIntRect;
use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods; use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
use super::canvasrenderingcontext2d::CanvasRenderingContext2D;
use super::clipboardevent::ClipboardEventType; use super::clipboardevent::ClipboardEventType;
use super::performancepainttiming::PerformancePaintTiming; use super::performancepainttiming::PerformancePaintTiming;
use crate::DomTypes; use crate::DomTypes;
@ -466,6 +468,8 @@ pub(crate) struct Document {
/// where `id` needs to match any of the registered ShadowRoots /// where `id` needs to match any of the registered ShadowRoots
/// hosting the media controls UI. /// hosting the media controls UI.
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>, media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
/// List of all context 2d IDs that need flushing.
dirty_2d_contexts: DomRefCell<HashMapTracedValues<CanvasId, Dom<CanvasRenderingContext2D>>>,
/// List of all WebGL context IDs that need flushing. /// List of all WebGL context IDs that need flushing.
dirty_webgl_contexts: dirty_webgl_contexts:
DomRefCell<HashMapTracedValues<WebGLContextId, Dom<WebGLRenderingContext>>>, DomRefCell<HashMapTracedValues<WebGLContextId, Dom<WebGLRenderingContext>>>,
@ -3298,6 +3302,21 @@ impl Document {
receiver.recv().unwrap(); receiver.recv().unwrap();
} }
pub(crate) fn add_dirty_2d_canvas(&self, context: &CanvasRenderingContext2D) {
self.dirty_2d_contexts
.borrow_mut()
.entry(context.context_id())
.or_insert_with(|| Dom::from_ref(context));
}
pub(crate) fn flush_dirty_2d_canvases(&self) {
self.dirty_2d_contexts
.borrow_mut()
.drain()
.filter(|(_, context)| context.onscreen())
.for_each(|(_, context)| context.update_rendering());
}
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
pub(crate) fn webgpu_contexts(&self) -> WebGPUContextsMap { pub(crate) fn webgpu_contexts(&self) -> WebGPUContextsMap {
self.webgpu_contexts.clone() self.webgpu_contexts.clone()
@ -3847,6 +3866,7 @@ impl Document {
shadow_roots: DomRefCell::new(HashSet::new()), shadow_roots: DomRefCell::new(HashSet::new()),
shadow_roots_styles_changed: Cell::new(false), shadow_roots_styles_changed: Cell::new(false),
media_controls: DomRefCell::new(HashMap::new()), media_controls: DomRefCell::new(HashMap::new()),
dirty_2d_contexts: DomRefCell::new(HashMapTracedValues::new()),
dirty_webgl_contexts: DomRefCell::new(HashMapTracedValues::new()), dirty_webgl_contexts: DomRefCell::new(HashMapTracedValues::new()),
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
webgpu_contexts: Rc::new(RefCell::new(HashMapTracedValues::new())), webgpu_contexts: Rc::new(RefCell::new(HashMapTracedValues::new())),

View file

@ -180,6 +180,21 @@ impl HTMLCanvasElement {
} }
} }
pub(crate) fn mark_as_dirty(&self) {
if let Some(ref context) = *self.context.borrow() {
match *context {
CanvasContext::Context2d(ref context) => context.mark_as_dirty(),
CanvasContext::WebGL(ref context) => context.mark_as_dirty(),
CanvasContext::WebGL2(ref context) => context.mark_as_dirty(),
#[cfg(feature = "webgpu")]
CanvasContext::WebGPU(ref context) => context.mark_as_dirty(),
CanvasContext::Placeholder(ref _context) => {
// TODO: Should this be marked as dirty?
},
}
}
}
pub(crate) fn set_natural_width(&self, value: u32, can_gc: CanGc) { pub(crate) fn set_natural_width(&self, value: u32, can_gc: CanGc) {
let value = if value > UNSIGNED_LONG_MAX { let value = if value > UNSIGNED_LONG_MAX {
DEFAULT_WIDTH DEFAULT_WIDTH

View file

@ -4,10 +4,8 @@
use std::cell::Cell; use std::cell::Cell;
use canvas_traits::canvas::{CanvasId, CanvasMsg, FromLayoutMsg};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use euclid::{Scale, Size2D}; use euclid::{Scale, Size2D};
use ipc_channel::ipc;
use script_bindings::reflector::Reflector; use script_bindings::reflector::Reflector;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use style_traits::CSSPixel; use style_traits::CSSPixel;
@ -61,16 +59,9 @@ impl PaintRenderingContext2D {
) )
} }
pub(crate) fn get_canvas_id(&self) -> CanvasId {
self.canvas_state.get_canvas_id()
}
/// Send update to canvas paint thread and returns [`ImageKey`] /// Send update to canvas paint thread and returns [`ImageKey`]
pub(crate) fn image_key(&self) -> ImageKey { pub(crate) fn image_key(&self) -> ImageKey {
let (sender, receiver) = ipc::channel().unwrap(); self.canvas_state.update_rendering();
let msg = CanvasMsg::FromLayout(FromLayoutMsg::UpdateImage(sender), self.get_canvas_id());
let _ = self.canvas_state.get_ipc_renderer().send(msg);
receiver.recv().unwrap();
self.canvas_state.image_key() self.canvas_state.image_key()
} }

View file

@ -1941,6 +1941,7 @@ impl Window {
let for_display = reflow_goal.needs_display(); let for_display = reflow_goal.needs_display();
if for_display { if for_display {
document.flush_dirty_webgl_canvases(); document.flush_dirty_webgl_canvases();
document.flush_dirty_2d_canvases();
} }
let pending_restyles = document.drain_pending_restyles(); let pending_restyles = document.drain_pending_restyles();

View file

@ -25,7 +25,6 @@ pub struct CanvasId(pub u64);
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum CanvasMsg { pub enum CanvasMsg {
Canvas2d(Canvas2dMsg, CanvasId), Canvas2d(Canvas2dMsg, CanvasId),
FromLayout(FromLayoutMsg, CanvasId),
FromScript(FromScriptMsg, CanvasId), FromScript(FromScriptMsg, CanvasId),
Recreate(Option<Size2D<u64>>, CanvasId), Recreate(Option<Size2D<u64>>, CanvasId),
Close(CanvasId), Close(CanvasId),
@ -74,10 +73,6 @@ pub enum Canvas2dMsg {
SetFont(FontStyleStruct), SetFont(FontStyleStruct),
SetTextAlign(TextAlign), SetTextAlign(TextAlign),
SetTextBaseline(TextBaseline), SetTextBaseline(TextBaseline),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FromLayoutMsg {
UpdateImage(IpcSender<()>), UpdateImage(IpcSender<()>),
} }

View file

@ -19,7 +19,6 @@ use app_units::Au;
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use base::Epoch; use base::Epoch;
use base::id::{BrowsingContextId, PipelineId, WebViewId}; use base::id::{BrowsingContextId, PipelineId, WebViewId};
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use constellation_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData}; use constellation_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData};
use euclid::Size2D; use euclid::Size2D;
use euclid::default::{Point2D, Rect}; use euclid::default::{Point2D, Rect};
@ -116,7 +115,7 @@ pub enum LayoutElementType {
pub enum HTMLCanvasDataSource { pub enum HTMLCanvasDataSource {
WebGL(ImageKey), WebGL(ImageKey),
Image((ImageKey, CanvasId, IpcSender<CanvasMsg>)), Image(ImageKey),
WebGPU(ImageKey), WebGPU(ImageKey),
/// transparent black /// transparent black
Empty, Empty,