mirror of
https://github.com/servo/servo
synced 2026-05-10 09:02:30 +02:00
Canvas expects input data to be in RGBA premultiplied format and WebRender already supports RGBA and BGRA data as long as they are premultiplied. Pre-multiplying up front allows: - Avoiding many conversions when painting images to canvas. - Passing the `RasterImage` IpcSharedMemory of the image instead of creating a new one with the premultiplied data every time we upload to WebRender. This is a big deal for animated gifs, because before every frame was creating a new shared memory segment. It seems that for rasterized SVGs were were already putting premultplied data into the `RasterImage` so it's quite likely SVGs were being composited incorrectly. Testing: This causes 8 tests to start passing and 2 tests to fail in the WebGL conformance suite. The failures are due to the fact that premultiplying alpha is lossy when alpha is 0. In that case, the resulting color of a blend operation might be wrong. This is typically only a problem if you use RGBA data as RGB data, which is pretty unusual. In the case that you are blending with RGBA the final color values will be 0 or close to 0 anyway. Gecko solves this issue by having a cacheable surface generation API that can fetch both premulitplied and unpremulitplied data from things like image elements. We do not have that yet, but I argue that this change is important anyway due to the amount that it reduces memory and file descriptor usage as well as the cost of copying image data so much in memory. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
119 lines
4.1 KiB
Rust
119 lines
4.1 KiB
Rust
/* 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 std::cell::Cell;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use compositing_traits::{ImageUpdate, SerializableImageData};
|
|
use layout_api::AnimatingImages;
|
|
use malloc_size_of::MallocSizeOf;
|
|
use parking_lot::RwLock;
|
|
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
|
|
use timers::{TimerEventRequest, TimerId};
|
|
|
|
use crate::dom::bindings::refcounted::Trusted;
|
|
use crate::dom::node::Node;
|
|
use crate::dom::window::Window;
|
|
use crate::script_thread::with_script_thread;
|
|
|
|
#[derive(Clone, Default, JSTraceable)]
|
|
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
|
pub struct ImageAnimationManager {
|
|
/// The set of [`AnimatingImages`] which is used to communicate the addition
|
|
/// and removal of animating images from layout.
|
|
#[no_trace]
|
|
animating_images: Arc<RwLock<AnimatingImages>>,
|
|
|
|
/// The [`TimerId`] of the currently scheduled animated image update callback.
|
|
#[no_trace]
|
|
callback_timer_id: Cell<Option<TimerId>>,
|
|
}
|
|
|
|
impl MallocSizeOf for ImageAnimationManager {
|
|
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
|
|
(*self.animating_images.read()).size_of(ops)
|
|
}
|
|
}
|
|
|
|
impl ImageAnimationManager {
|
|
pub(crate) fn animating_images(&self) -> Arc<RwLock<AnimatingImages>> {
|
|
self.animating_images.clone()
|
|
}
|
|
|
|
fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
|
|
self.animating_images
|
|
.read()
|
|
.node_to_state_map
|
|
.values()
|
|
.map(|state| state.duration_to_next_frame(now))
|
|
.min()
|
|
}
|
|
|
|
pub(crate) fn update_active_frames(&self, window: &Window, now: f64) {
|
|
if self.animating_images.read().is_empty() {
|
|
return;
|
|
}
|
|
|
|
let updates = self
|
|
.animating_images
|
|
.write()
|
|
.node_to_state_map
|
|
.values_mut()
|
|
.filter_map(|state| {
|
|
if !state.update_frame_for_animation_timeline_value(now) {
|
|
return None;
|
|
}
|
|
|
|
let image = &state.image;
|
|
let (descriptor, ipc_shared_memory) =
|
|
image.webrender_image_descriptor_and_data_for_frame(state.active_frame);
|
|
|
|
Some(ImageUpdate::UpdateImage(
|
|
image.id.unwrap(),
|
|
descriptor,
|
|
SerializableImageData::Raw(ipc_shared_memory),
|
|
None,
|
|
))
|
|
})
|
|
.collect();
|
|
window.compositor_api().update_images(updates);
|
|
|
|
self.maybe_schedule_update(window, now);
|
|
}
|
|
|
|
/// After doing a layout, if the set of animating images was updated in some way,
|
|
/// schedule a new animation update.
|
|
pub(crate) fn maybe_schedule_update_after_layout(&self, window: &Window, now: f64) {
|
|
if self.animating_images().write().clear_dirty() {
|
|
self.maybe_schedule_update(window, now);
|
|
}
|
|
}
|
|
|
|
fn maybe_schedule_update(&self, window: &Window, now: f64) {
|
|
with_script_thread(|script_thread| {
|
|
if let Some(current_timer_id) = self.callback_timer_id.take() {
|
|
self.callback_timer_id.set(None);
|
|
script_thread.cancel_timer(current_timer_id);
|
|
}
|
|
|
|
if let Some(duration) = self.duration_to_next_frame(now) {
|
|
let trusted_window = Trusted::new(window);
|
|
let timer_id = script_thread.schedule_timer(TimerEventRequest {
|
|
callback: Box::new(move || {
|
|
let window = trusted_window.root();
|
|
window.Document().set_has_pending_animated_image_update();
|
|
}),
|
|
duration,
|
|
});
|
|
self.callback_timer_id.set(Some(timer_id));
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
|
|
self.animating_images().write().remove(node.to_opaque());
|
|
}
|
|
}
|