Add a basic caching mechanism for ImageKeys. (#37369)

This creates a new method in shared/compositing/lib to generate image
keys that are send over the webview. This does not immediately return
the keys but goes over the constellation to receive the keys from the
IOCompositor. To make this more efficient, we now cache the keys in
image_cache in a simple FIFO order. The old blocking method stays intact
for now but got renamed to make the blocking clear.
The blocking calls that are left are in:
- `components/canvas/canvas_data.rs`
- `components/script/dom/htmlmediaelement.rs`

Testing: WPT tests should cover this as this doesn't change any
functionality.
Fixes: Was mentioned in
https://github.com/servo/servo/issues/37161#issuecomment-2915750051 and
part of https://github.com/servo/servo/issues/37086

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>
Co-authored-by: gterzian <2792687+gterzian@users.noreply.github.com>
This commit is contained in:
Narfinger 2025-07-03 22:16:43 +09:00 committed by GitHub
parent 89bfa26f00
commit ca47cc2fa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 392 additions and 70 deletions

View file

@ -421,7 +421,7 @@ impl<'a, B: Backend> CanvasData<'a, B> {
) -> CanvasData<'a, B> {
let size = size.max(MIN_WR_IMAGE_SIZE);
let draw_target = backend.create_drawtarget(size);
let image_key = compositor_api.generate_image_key().unwrap();
let image_key = compositor_api.generate_image_key_blocking().unwrap();
let descriptor = ImageDescriptor {
size: size.cast().cast_unit(),
stride: None,

View file

@ -38,7 +38,7 @@ use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind};
use profile_traits::time::{self as profile_time, ProfilerCategory};
use profile_traits::{path, time_profile};
use servo_config::opts;
use servo_config::{opts, pref};
use servo_geometry::DeviceIndependentPixel;
use style_traits::CSSPixel;
use webrender::{CaptureBits, RenderApi, Transaction};
@ -896,6 +896,19 @@ impl IOCompositor {
let _ = sender.send(self.global.borrow().webrender_api.generate_image_key());
},
CompositorMsg::GenerateImageKeysForPipeline(pipeline_id) => {
let image_keys = (0..pref!(image_key_batch_size))
.map(|_| self.global.borrow().webrender_api.generate_image_key())
.collect();
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::SendImageKeysForPipeline(
pipeline_id,
image_keys,
),
) {
warn!("Sending Image Keys to Constellation failed with({error:?}).");
}
},
CompositorMsg::UpdateImages(updates) => {
let mut txn = Transaction::new();
for update in updates {

View file

@ -59,6 +59,7 @@ mod from_constellation {
Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"),
Self::CollectMemoryReport(..) => target!("CollectMemoryReport"),
Self::Viewport(..) => target!("Viewport"),
Self::GenerateImageKeysForPipeline(..) => target!("GenerateImageKeysForPipeline"),
}
}
}

View file

@ -149,6 +149,8 @@ pub struct Preferences {
/// Whether or not subpixel antialiasing is enabled for text rendering.
pub gfx_subpixel_text_antialiasing_enabled: bool,
pub gfx_texture_swizzling_enabled: bool,
/// The amount of image keys we request per batch for the image cache.
pub image_key_batch_size: i64,
/// Whether or not the DOM inspector should show shadow roots of user-agent shadow trees
pub inspector_show_servo_internal_shadow_roots: bool,
pub js_asmjs_enabled: bool,
@ -326,6 +328,7 @@ impl Preferences {
gfx_text_antialiasing_enabled: true,
gfx_subpixel_text_antialiasing_enabled: true,
gfx_texture_swizzling_enabled: true,
image_key_batch_size: 10,
inspector_show_servo_internal_shadow_roots: false,
js_asmjs_enabled: true,
js_asyncstack: false,

View file

@ -1448,6 +1448,26 @@ where
EmbedderToConstellationMessage::CreateMemoryReport(sender) => {
self.mem_profiler_chan.send(ProfilerMsg::Report(sender));
},
EmbedderToConstellationMessage::SendImageKeysForPipeline(pipeline_id, image_keys) => {
if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
if pipeline
.event_loop
.send(ScriptThreadMessage::SendImageKeysBatch(
pipeline_id,
image_keys,
))
.is_err()
{
warn!("Could not send image keys to pipeline {:?}", pipeline_id);
}
} else {
warn!(
"Keys were generated for a pipeline ({:?}) that was
closed before the request could be fulfilled.",
pipeline_id
)
}
},
}
}

View file

@ -76,6 +76,7 @@ mod from_compositor {
Self::PaintMetric(..) => target!("PaintMetric"),
Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"),
Self::CreateMemoryReport(..) => target!("CreateMemoryReport"),
Self::SendImageKeysForPipeline(..) => target!("SendImageKeysForPipeline"),
}
}
}

View file

@ -2,8 +2,9 @@
* 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::collections::HashMap;
use std::cmp::min;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use std::{mem, thread};
@ -11,7 +12,7 @@ use base::id::PipelineId;
use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
use imsz::imsz_from_reader;
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
use log::{debug, warn};
use log::{debug, error, warn};
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use mime::Mime;
@ -29,7 +30,9 @@ use resvg::{tiny_skia, usvg};
use servo_config::pref;
use servo_url::{ImmutableOrigin, ServoUrl};
use webrender_api::units::DeviceIntSize;
use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
use webrender_api::{
ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey as WebRenderImageKey,
};
use crate::resource_thread::CoreResourceThreadPool;
@ -95,6 +98,8 @@ fn decode_bytes_sync(
DecoderMsg { key, image }
}
/// This will block on getting an ImageKey
/// but that is ok because it is done once upon start-up of a script-thread.
fn get_placeholder_image(
compositor_api: &CrossProcessCompositorApi,
data: &[u8],
@ -102,11 +107,18 @@ fn get_placeholder_image(
let mut image = load_from_memory(data, CorsStatus::Unsafe)
.or_else(|| load_from_memory(FALLBACK_RIPPY, CorsStatus::Unsafe))
.expect("load fallback image failed");
set_webrender_image_key(compositor_api, &mut image);
let image_key = compositor_api
.generate_image_key_blocking()
.expect("Could not generate image key");
set_webrender_image_key(compositor_api, &mut image, image_key);
Arc::new(image)
}
fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &mut RasterImage) {
fn set_webrender_image_key(
compositor_api: &CrossProcessCompositorApi,
image: &mut RasterImage,
image_key: WebRenderImageKey,
) {
if image.id.is_some() {
return;
}
@ -146,12 +158,10 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m
offset: 0,
flags,
};
if let Some(image_key) = compositor_api.generate_image_key() {
let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(&bytes));
compositor_api.add_image(image_key, descriptor, data);
image.id = Some(image_key);
}
}
// ======================================================================
// Aux structs and enums.
@ -404,6 +414,51 @@ struct RasterizationTask {
result: Option<RasterImage>,
}
/// Used for storing images that do not have a `WebRenderImageKey` yet.
#[derive(Debug, MallocSizeOf)]
enum PendingKey {
RasterImage((LoadKey, RasterImage)),
Svg((LoadKey, RasterImage, DeviceIntSize)),
}
/// The state of the `WebRenderImageKey`` cache
#[derive(Debug, MallocSizeOf)]
enum KeyCacheState {
/// We already requested a batch of keys.
PendingBatch,
/// We have some keys in the cache.
Ready(Vec<WebRenderImageKey>),
}
impl KeyCacheState {
fn size(&self) -> usize {
match self {
KeyCacheState::PendingBatch => 0,
KeyCacheState::Ready(items) => items.len(),
}
}
}
/// As getting new keys takes a round trip over the constellation, we keep a small cache of them.
/// Additionally, this cache will store image resources that do not have a key yet because those
/// are needed to complete the load.
#[derive(MallocSizeOf)]
struct KeyCache {
/// A cache of `WebRenderImageKey`.
cache: KeyCacheState,
/// These images are loaded but have no key assigned to yet.
images_pending_keys: VecDeque<PendingKey>,
}
impl KeyCache {
fn new() -> Self {
KeyCache {
cache: KeyCacheState::Ready(Vec::new()),
images_pending_keys: VecDeque::new(),
}
}
}
/// ## Image cache implementation.
#[derive(MallocSizeOf)]
struct ImageCacheStore {
@ -433,33 +488,126 @@ struct ImageCacheStore {
/// Cross-process compositor API instance.
#[ignore_malloc_size_of = "Channel from another crate"]
compositor_api: CrossProcessCompositorApi,
// The PipelineId will initially be None because the constructed cache is not associated
// with any pipeline yet. This will happen later by way of `create_new_image_cache`.
pipeline_id: Option<PipelineId>,
/// Main struct to handle the cache of `WebRenderImageKey` and
/// images that do not have a key yet.
key_cache: KeyCache,
}
impl ImageCacheStore {
// Change state of a url from pending -> loaded.
fn complete_load(&mut self, key: LoadKey, mut load_result: LoadResult) {
/// Finishes loading the image by setting the WebRenderImageKey and calling `compete_load` or `complete_load_svg`.
fn set_key_and_finish_load(&mut self, pending_image: PendingKey, image_key: WebRenderImageKey) {
match pending_image {
PendingKey::RasterImage((pending_id, mut raster_image)) => {
set_webrender_image_key(&self.compositor_api, &mut raster_image, image_key);
self.complete_load(pending_id, LoadResult::LoadedRasterImage(raster_image));
},
PendingKey::Svg((pending_id, mut raster_image, requested_size)) => {
set_webrender_image_key(&self.compositor_api, &mut raster_image, image_key);
self.complete_load_svg(raster_image, pending_id, requested_size);
},
}
}
/// If a key is available the image will be immediately loaded, otherwise it will load then the next batch of
/// keys is received. Only call this if the image does not have a `LoadKey` yet.
fn load_image_with_keycache(&mut self, pending_image: PendingKey) {
if let Some(pipeline_id) = self.pipeline_id {
match self.key_cache.cache {
KeyCacheState::PendingBatch => {
self.key_cache.images_pending_keys.push_back(pending_image);
},
KeyCacheState::Ready(ref mut cache) => match cache.pop() {
Some(image_key) => {
self.set_key_and_finish_load(pending_image, image_key);
},
None => {
self.key_cache.images_pending_keys.push_back(pending_image);
self.compositor_api.generate_image_key_async(pipeline_id);
self.key_cache.cache = KeyCacheState::PendingBatch
},
},
}
} else {
error!("No pipeline id for this image key cache.");
}
}
/// Insert received keys into the cache and complete the loading of images.
fn insert_keys_and_load_images(&mut self, image_keys: Vec<WebRenderImageKey>) {
if let KeyCacheState::PendingBatch = self.key_cache.cache {
self.key_cache.cache = KeyCacheState::Ready(image_keys);
let len = min(
self.key_cache.cache.size(),
self.key_cache.images_pending_keys.len(),
);
let images = self
.key_cache
.images_pending_keys
.drain(0..len)
.collect::<Vec<PendingKey>>();
for key in images {
self.load_image_with_keycache(key);
}
if !self.key_cache.images_pending_keys.is_empty() {
self.compositor_api
.generate_image_key_async(self.pipeline_id.unwrap());
self.key_cache.cache = KeyCacheState::PendingBatch
}
} else {
unreachable!("A batch was received while we didn't request one")
}
}
/// Complete the loading the of the rasterized svg image. This needs the `RasterImage` to
/// already have a `WebRenderImageKey`.
fn complete_load_svg(
&mut self,
rasterized_image: RasterImage,
pending_image_id: PendingImageId,
requested_size: DeviceIntSize,
) {
let listeners = {
self.rasterized_vector_images
.get_mut(&(pending_image_id, requested_size))
.map(|task| {
task.result = Some(rasterized_image);
std::mem::take(&mut task.listeners)
})
.unwrap_or_default()
};
for (pipeline_id, sender) in listeners {
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
RasterizationCompleteResponse {
pipeline_id,
image_id: pending_image_id,
requested_size,
},
));
}
}
/// The rest of complete load. This requires that images have a valid `WebRenderImageKey`.
fn complete_load(&mut self, key: LoadKey, load_result: LoadResult) {
debug!("Completed decoding for {:?}", load_result);
let pending_load = match self.pending_loads.remove(&key) {
Some(load) => load,
None => return,
};
match load_result {
LoadResult::LoadedRasterImage(ref mut raster_image) => {
set_webrender_image_key(&self.compositor_api, raster_image)
},
LoadResult::LoadedVectorImage(ref vector_image) => {
self.vector_images.insert(key, vector_image.clone());
},
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
}
let url = pending_load.final_url.clone();
let image_response = match load_result {
LoadResult::LoadedRasterImage(raster_image) => {
assert!(raster_image.id.is_some());
ImageResponse::Loaded(Image::Raster(Arc::new(raster_image)), url.unwrap())
},
LoadResult::LoadedVectorImage(vector_image) => {
self.vector_images.insert(key, vector_image.clone());
let natural_dimensions = vector_image.svg_tree.size().to_int_size();
let metadata = ImageMetadata {
width: natural_dimensions.width(),
@ -523,7 +671,10 @@ impl ImageCacheStore {
fn handle_decoder(&mut self, msg: DecoderMsg) {
let image = match msg.image {
None => LoadResult::None,
Some(DecodedImage::Raster(raster_image)) => LoadResult::LoadedRasterImage(raster_image),
Some(DecodedImage::Raster(raster_image)) => {
self.load_image_with_keycache(PendingKey::RasterImage((msg.key, raster_image)));
return;
},
Some(DecodedImage::Vector(vector_image_data)) => {
LoadResult::LoadedVectorImage(vector_image_data)
},
@ -559,7 +710,9 @@ impl ImageCache for ImageCacheImpl {
rasterized_vector_images: HashMap::new(),
placeholder_image: get_placeholder_image(&compositor_api, &rippy_data),
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
compositor_api,
compositor_api: compositor_api.clone(),
pipeline_id: None,
key_cache: KeyCache::new(),
})),
thread_pool: Arc::new(CoreResourceThreadPool::new(
thread_count,
@ -623,7 +776,7 @@ impl ImageCache for ImageCacheImpl {
}
}
let decoded = {
let (key, decoded) = {
let result = store
.pending_loads
.get_cached(url.clone(), origin.clone(), cors_setting);
@ -631,11 +784,14 @@ impl ImageCache for ImageCacheImpl {
CacheResult::Hit(key, pl) => match (&pl.result, &pl.metadata) {
(&Some(Ok(_)), _) => {
debug!("Sync decoding {} ({:?})", url, key);
(
key,
decode_bytes_sync(
key,
pl.bytes.as_slice(),
pl.cors_status,
pl.content_type.clone(),
),
)
},
(&None, Some(meta)) => {
@ -674,7 +830,8 @@ impl ImageCache for ImageCacheImpl {
is_placeholder,
})
},
_ => ImageCacheResult::LoadError,
// Note: this happens if we are pending a batch of image keys.
_ => ImageCacheResult::Pending(key),
}
}
@ -767,7 +924,7 @@ impl ImageCache for ImageCacheImpl {
height: tinyskia_requested_size.height(),
};
let mut rasterized_image = RasterImage {
let rasterized_image = RasterImage {
metadata: ImageMetadata {
width: tinyskia_requested_size.width(),
height: tinyskia_requested_size.height(),
@ -779,28 +936,12 @@ impl ImageCache for ImageCacheImpl {
cors_status: vector_image.cors_status,
};
let listeners = {
let mut store = store.lock().unwrap();
set_webrender_image_key(&store.compositor_api, &mut rasterized_image);
store
.rasterized_vector_images
.get_mut(&(image_id, requested_size))
.map(|task| {
task.result = Some(rasterized_image);
std::mem::take(&mut task.listeners)
})
.unwrap_or_default()
};
for (pipeline_id, sender) in listeners {
let _ = sender.send(ImageCacheResponseMessage::VectorImageRasterizationComplete(
RasterizationCompleteResponse {
pipeline_id,
store.load_image_with_keycache(PendingKey::Svg((
image_id,
rasterized_image,
requested_size,
},
));
}
)));
});
None
@ -905,6 +1046,7 @@ impl ImageCache for ImageCacheImpl {
fn create_new_image_cache(
&self,
pipeline_id: Option<PipelineId>,
compositor_api: CrossProcessCompositorApi,
) -> Arc<dyn ImageCache> {
let store = self.store.lock().unwrap();
@ -919,10 +1061,17 @@ impl ImageCache for ImageCacheImpl {
compositor_api,
vector_images: HashMap::new(),
rasterized_vector_images: HashMap::new(),
key_cache: KeyCache::new(),
pipeline_id,
})),
thread_pool: self.thread_pool.clone(),
})
}
fn fill_key_cache_with_batch_of_keys(&self, image_keys: Vec<WebRenderImageKey>) {
let mut store = self.store.lock().unwrap();
store.insert_keys_and_load_images(image_keys);
}
}
impl Drop for ImageCacheStore {

View file

@ -237,7 +237,7 @@ impl VideoFrameRenderer for MediaFrameRenderer {
Some(current_frame) => {
self.old_frame = Some(current_frame.image_key);
let Some(new_image_key) = self.compositor_api.generate_image_key() else {
let Some(new_image_key) = self.compositor_api.generate_image_key_blocking() else {
return;
};
@ -270,7 +270,7 @@ impl VideoFrameRenderer for MediaFrameRenderer {
updates.push(ImageUpdate::AddImage(new_image_key, descriptor, image_data));
},
None => {
let Some(image_key) = self.compositor_api.generate_image_key() else {
let Some(image_key) = self.compositor_api.generate_image_key_blocking() else {
return;
};

View file

@ -12,7 +12,7 @@ use crate::dom::bindings::codegen::Bindings::TestWorkletBinding::TestWorkletMeth
use crate::dom::bindings::codegen::Bindings::WorkletBinding::Worklet_Binding::WorkletMethods;
use crate::dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::promise::Promise;
@ -70,7 +70,7 @@ impl TestWorkletMethods<crate::DomTypeHolder> for TestWorklet {
fn Lookup(&self, key: DOMString) -> Option<DOMString> {
let id = self.worklet.worklet_id();
let pool = ScriptThread::worklet_thread_pool();
let pool = ScriptThread::worklet_thread_pool(self.global().image_cache());
pool.test_worklet_lookup(id, String::from(key))
.map(DOMString::from)
}

View file

@ -37,7 +37,7 @@ use crate::dom::bindings::codegen::Bindings::WorkletBinding::{WorkletMethods, Wo
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::TrustedPromise;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot, RootCollection, ThreadLocalStackRoots};
use crate::dom::bindings::str::USVString;
use crate::dom::bindings::trace::{CustomTraceable, JSTraceable, RootedTraceableBox};
@ -153,7 +153,7 @@ impl WorkletMethods<crate::DomTypeHolder> for Worklet {
self.droppable_field
.thread_pool
.get_or_init(ScriptThread::worklet_thread_pool)
.get_or_init(|| ScriptThread::worklet_thread_pool(self.global().image_cache()))
.fetch_and_invoke_a_worklet_script(
self.window.pipeline_id(),
self.droppable_field.worklet_id,

View file

@ -92,6 +92,7 @@ impl MixedMessage {
ScriptThreadMessage::SetWebGPUPort(..) => None,
ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id),
ScriptThreadMessage::EvaluateJavaScript(id, _, _) => Some(*id),
ScriptThreadMessage::SendImageKeysBatch(..) => None,
},
MixedMessage::FromScript(inner_msg) => match inner_msg {
MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {

View file

@ -735,7 +735,8 @@ impl ScriptThread {
})
}
pub(crate) fn worklet_thread_pool() -> Rc<WorkletThreadPool> {
/// The worklet will use the given `ImageCache`.
pub(crate) fn worklet_thread_pool(image_cache: Arc<dyn ImageCache>) -> Rc<WorkletThreadPool> {
with_optional_script_thread(|script_thread| {
let script_thread = script_thread.unwrap();
script_thread
@ -752,9 +753,7 @@ impl ScriptThread {
.senders
.pipeline_to_constellation_sender
.clone(),
image_cache: script_thread
.image_cache
.create_new_image_cache(script_thread.compositor_api.clone()),
image_cache,
#[cfg(feature = "webgpu")]
gpu_id_hub: script_thread.gpu_id_hub.clone(),
inherited_secure_context: script_thread.inherited_secure_context,
@ -1998,6 +1997,18 @@ impl ScriptThread {
ScriptThreadMessage::EvaluateJavaScript(pipeline_id, evaluation_id, script) => {
self.handle_evaluate_javascript(pipeline_id, evaluation_id, script, can_gc);
},
ScriptThreadMessage::SendImageKeysBatch(pipeline_id, image_keys) => {
if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
window
.image_cache()
.fill_key_cache_with_batch_of_keys(image_keys);
} else {
warn!(
"Could not find window corresponding to an image cache to send image keys to pipeline {:?}",
pipeline_id
);
}
},
}
}
@ -3321,7 +3332,7 @@ impl ScriptThread {
let image_cache = self
.image_cache
.create_new_image_cache(self.compositor_api.clone());
.create_new_image_cache(Some(incomplete.pipeline_id), self.compositor_api.clone());
let layout_config = LayoutConfig {
id: incomplete.pipeline_id,

View file

@ -148,6 +148,9 @@ pub enum CompositorMsg {
/// Create a new image key. The result will be returned via the
/// provided channel sender.
GenerateImageKey(IpcSender<ImageKey>),
/// The same as the above but it will be forwarded to the pipeline instead
/// of send via a channel.
GenerateImageKeysForPipeline(PipelineId),
/// Perform a resource update operation.
UpdateImages(SmallVec<[ImageUpdate; 1]>),
@ -294,12 +297,24 @@ impl CrossProcessCompositorApi {
}
/// Create a new image key. Blocks until the key is available.
pub fn generate_image_key(&self) -> Option<ImageKey> {
pub fn generate_image_key_blocking(&self) -> Option<ImageKey> {
let (sender, receiver) = ipc::channel().unwrap();
self.0.send(CompositorMsg::GenerateImageKey(sender)).ok()?;
receiver.recv().ok()
}
/// Sends a message to the compositor for creating new image keys.
/// The compositor will then send a batch of keys over the constellation to the script_thread
/// and the appropriate pipeline.
pub fn generate_image_key_async(&self, pipeline_id: PipelineId) {
if let Err(e) = self
.0
.send(CompositorMsg::GenerateImageKeysForPipeline(pipeline_id))
{
warn!("Could not send image keys to Compositor {}", e);
}
}
pub fn add_image(
&self,
key: ImageKey,

View file

@ -30,8 +30,8 @@ use serde::{Deserialize, Serialize};
use servo_url::{ImmutableOrigin, ServoUrl};
pub use structured_data::*;
use strum_macros::IntoStaticStr;
use webrender_api::ExternalScrollId;
use webrender_api::units::LayoutVector2D;
use webrender_api::{ExternalScrollId, ImageKey};
/// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or
/// from `libservo` itself.
@ -94,6 +94,8 @@ pub enum EmbedderToConstellationMessage {
EvaluateJavaScript(WebViewId, JavaScriptEvaluationId, String),
/// Create a memory report and return it via the ipc sender
CreateMemoryReport(IpcSender<MemoryReportResult>),
/// Sends the generated image key to the image cache associated with this pipeline.
SendImageKeysForPipeline(PipelineId, Vec<ImageKey>),
}
/// A description of a paint metric that is sent from the Servo renderer to the

View file

@ -14,6 +14,7 @@ use pixels::{CorsStatus, ImageMetadata, RasterImage};
use profile_traits::mem::Report;
use serde::{Deserialize, Serialize};
use servo_url::{ImmutableOrigin, ServoUrl};
use webrender_api::ImageKey;
use webrender_api::units::DeviceIntSize;
use crate::FetchResponseMsg;
@ -225,6 +226,10 @@ pub trait ImageCache: Sync + Send {
/// Create new image cache based on this one, while reusing the existing thread_pool.
fn create_new_image_cache(
&self,
pipeline_id: Option<PipelineId>,
compositor_api: CrossProcessCompositorApi,
) -> Arc<dyn ImageCache>;
/// Fills the image cache with a batch of keys.
fn fill_key_cache_with_batch_of_keys(&self, image_keys: Vec<ImageKey>);
}

View file

@ -251,6 +251,8 @@ pub enum ScriptThreadMessage {
/// Evaluate the given JavaScript and return a result via a corresponding message
/// to the Constellation.
EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String),
/// A new batch of keys for the image cache for the specific pipeline.
SendImageKeysBatch(PipelineId, Vec<ImageKey>),
}
impl fmt::Debug for ScriptThreadMessage {

View file

@ -907,7 +907,7 @@ impl WebGLThread {
let descriptor = Self::image_descriptor(size, alpha);
let data = Self::external_image_data(context_id, image_buffer_kind);
let image_key = compositor_api.generate_image_key().unwrap();
let image_key = compositor_api.generate_image_key_blocking().unwrap();
compositor_api.add_image(image_key, descriptor, data);
image_key

View file

@ -502,7 +502,7 @@ impl WGPU {
.lock()
.expect("Lock poisoned?")
.next_id(WebrenderImageHandlerType::WebGPU);
let image_key = self.compositor_api.generate_image_key().unwrap();
let image_key = self.compositor_api.generate_image_key_blocking().unwrap();
let context_id = WebGPUContextId(id.0);
if let Err(e) = sender.send((context_id, image_key)) {
warn!("Failed to send ExternalImageId to new context ({})", e);

View file

@ -10756,6 +10756,50 @@
"9235007d960cc6c804a93c89f24881bedc3613c3",
[]
],
"test1.jpg": [
"124d70729ea649ee253254ec0f1e2ea0f926ed31",
[]
],
"test10.jpg": [
"1ff396cd08dbf8a6039f39e79af860c44859da48",
[]
],
"test11.jpg": [
"8074fffaa33a371825c7a54c9536fc22f4d8bca4",
[]
],
"test2.jpg": [
"e35589b82207d655379acbcac3932ccbef846dee",
[]
],
"test3.jpg": [
"1f135409c45ea58b4cb7b89bdf4f71b8b43d0b88",
[]
],
"test4.jpg": [
"793ded09d3e65db01ea2c55c6f9f2bbe10366b94",
[]
],
"test5.jpg": [
"3df583072e57fd6195104e8fb16be7a91b751a70",
[]
],
"test6.jpg": [
"81c68f412a27fcdcb1d961db568168b0e9ea2caf",
[]
],
"test7.jpg": [
"b6169d4a7b804b184e4677eb9eda54c67f0d2e75",
[]
],
"test8.jpg": [
"295e9c523527c584eac494298060d2631d69603d",
[]
],
"test9.jpg": [
"c82f94f0134105792bb45a3a827c2a51cc92e674",
[]
],
"text-overflow-ellipsis-stacking-context-ref.html": [
"14215e780ab4a0cf00ef23b8472636a393aeacf1",
[]
@ -13525,6 +13569,13 @@
{}
]
],
"img_load_more_than_cache.html": [
"41f1212f8df42c9082d462d861a1ae5545ea5523",
[
null,
{}
]
],
"img_multiple_request.html": [
"df625a2bc338c0220808cf7a153128fe9b9d48a8",
[

View file

@ -0,0 +1,48 @@
<!doctype html>
<meta charset="utf-8">
<title>Test Loading more images than keys obtained in a batch by the image cache</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<img src="test1.jpg" id="img1">
<img src="test2.jpg" id="img2">
<img src="test3.jpg" id="img3">
<img src="test4.jpg" id="img4">
<img src="test5.jpg" id="img5">
<img src="test6.jpg" id="img6">
<img src="test7.jpg" id="img7">
<img src="test8.jpg" id="img8">
<img src="test9.jpg" id="img9">
<img src="test10.jpg" id="img10">
<img src="test11.jpg" id="img11">
<script>
promise_test(function() {
let image_tags = [];
image_tags.push(document.getElementById('img1'));
image_tags.push(document.getElementById('img2'));
image_tags.push(document.getElementById('img3'));
image_tags.push(document.getElementById('img4'));
image_tags.push(document.getElementById('img5'));
image_tags.push(document.getElementById('img6'));
image_tags.push(document.getElementById('img7'));
image_tags.push(document.getElementById('img8'));
image_tags.push(document.getElementById('img9'));
image_tags.push(document.getElementById('img10'));
image_tags.push(document.getElementById('img11'));
return Promise.all(image_tags.map((elem) => {
if (elem.complete === true) {
return Promise.resolve(true)
} else {
return new Promise(resolve => {
elem.onload = () => resolve(true);
elem.onerror = () => resolve(false);
})
}
})).then(function(values) {
assert_equals(values.every((elem, index, array) => elem), true)
});
})
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB