diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 0e54cd2ce0f..978fb44c3bd 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -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, diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 81a17b5dfda..7be3b333563 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -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 { diff --git a/components/compositing/tracing.rs b/components/compositing/tracing.rs index a130eaaa242..f51bf77013d 100644 --- a/components/compositing/tracing.rs +++ b/components/compositing/tracing.rs @@ -59,6 +59,7 @@ mod from_constellation { Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"), Self::CollectMemoryReport(..) => target!("CollectMemoryReport"), Self::Viewport(..) => target!("Viewport"), + Self::GenerateImageKeysForPipeline(..) => target!("GenerateImageKeysForPipeline"), } } } diff --git a/components/config/prefs.rs b/components/config/prefs.rs index a94daba8d34..486a3d40c7b 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -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, diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 14c52df80fa..19d273d84c8 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -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 + ) + } + }, } } diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index 473edd4cd20..d164319a733 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -76,6 +76,7 @@ mod from_compositor { Self::PaintMetric(..) => target!("PaintMetric"), Self::EvaluateJavaScript(..) => target!("EvaluateJavaScript"), Self::CreateMemoryReport(..) => target!("CreateMemoryReport"), + Self::SendImageKeysForPipeline(..) => target!("SendImageKeysForPipeline"), } } } diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index aa484fc3e45..8df2b277afc 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -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,11 +158,9 @@ 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); - } + let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(&bytes)); + compositor_api.add_image(image_key, descriptor, data); + image.id = Some(image_key); } // ====================================================================== @@ -404,6 +414,51 @@ struct RasterizationTask { result: Option, } +/// 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), +} + +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, +} + +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, + + /// 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) { + 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::>(); + 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); - decode_bytes_sync( + ( key, - pl.bytes.as_slice(), - pl.cors_status, - pl.content_type.clone(), + 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, - image_id, - requested_size, - }, - )); - } + let mut store = store.lock().unwrap(); + 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, compositor_api: CrossProcessCompositorApi, ) -> Arc { 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) { + let mut store = self.store.lock().unwrap(); + store.insert_keys_and_load_images(image_keys); + } } impl Drop for ImageCacheStore { diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index cc1c998db93..32b2dd3e8a8 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -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; }; diff --git a/components/script/dom/testworklet.rs b/components/script/dom/testworklet.rs index 7c7630ac788..6729058345d 100644 --- a/components/script/dom/testworklet.rs +++ b/components/script/dom/testworklet.rs @@ -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 for TestWorklet { fn Lookup(&self, key: DOMString) -> Option { 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) } diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs index be0a588b279..9d5e5cbafcb 100644 --- a/components/script/dom/worklet.rs +++ b/components/script/dom/worklet.rs @@ -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 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, diff --git a/components/script/messaging.rs b/components/script/messaging.rs index a3d0e0046f1..8294d977944 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -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, _)) => { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 3ea10ca9bdc..1826e747e65 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -735,7 +735,8 @@ impl ScriptThread { }) } - pub(crate) fn worklet_thread_pool() -> Rc { + /// The worklet will use the given `ImageCache`. + pub(crate) fn worklet_thread_pool(image_cache: Arc) -> Rc { 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, diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 607dda6cfe9..c9d2fe861bf 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -148,6 +148,9 @@ pub enum CompositorMsg { /// Create a new image key. The result will be returned via the /// provided channel sender. GenerateImageKey(IpcSender), + /// 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 { + pub fn generate_image_key_blocking(&self) -> Option { 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, diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index 95e90a95cb4..9e31e99c58b 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -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), + /// Sends the generated image key to the image cache associated with this pipeline. + SendImageKeysForPipeline(PipelineId, Vec), } /// A description of a paint metric that is sent from the Servo renderer to the diff --git a/components/shared/net/image_cache.rs b/components/shared/net/image_cache.rs index eab10475e9a..cd98fc5b6e1 100644 --- a/components/shared/net/image_cache.rs +++ b/components/shared/net/image_cache.rs @@ -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, compositor_api: CrossProcessCompositorApi, ) -> Arc; + + /// Fills the image cache with a batch of keys. + fn fill_key_cache_with_batch_of_keys(&self, image_keys: Vec); } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 3086d10bc88..74d264e3697 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -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), } impl fmt::Debug for ScriptThreadMessage { diff --git a/components/webgl/webgl_thread.rs b/components/webgl/webgl_thread.rs index 77ee8f507cd..b5af3fdb68b 100644 --- a/components/webgl/webgl_thread.rs +++ b/components/webgl/webgl_thread.rs @@ -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 diff --git a/components/webgpu/wgpu_thread.rs b/components/webgpu/wgpu_thread.rs index 6efc63a6e70..573be890eb8 100644 --- a/components/webgpu/wgpu_thread.rs +++ b/components/webgpu/wgpu_thread.rs @@ -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); diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 72c56c27fda..0fd6d74c3b1 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -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", [ diff --git a/tests/wpt/mozilla/tests/mozilla/img_load_more_than_cache.html b/tests/wpt/mozilla/tests/mozilla/img_load_more_than_cache.html new file mode 100644 index 00000000000..41f1212f8df --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/img_load_more_than_cache.html @@ -0,0 +1,48 @@ + + + Test Loading more images than keys obtained in a batch by the image cache + + + + + + + + + + + + + + + + + diff --git a/tests/wpt/mozilla/tests/mozilla/test1.jpg b/tests/wpt/mozilla/tests/mozilla/test1.jpg new file mode 100644 index 00000000000..124d70729ea Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test1.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test10.jpg b/tests/wpt/mozilla/tests/mozilla/test10.jpg new file mode 100644 index 00000000000..1ff396cd08d Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test10.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test11.jpg b/tests/wpt/mozilla/tests/mozilla/test11.jpg new file mode 100644 index 00000000000..8074fffaa33 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test11.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test2.jpg b/tests/wpt/mozilla/tests/mozilla/test2.jpg new file mode 100644 index 00000000000..e35589b8220 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test2.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test3.jpg b/tests/wpt/mozilla/tests/mozilla/test3.jpg new file mode 100644 index 00000000000..1f135409c45 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test3.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test4.jpg b/tests/wpt/mozilla/tests/mozilla/test4.jpg new file mode 100644 index 00000000000..793ded09d3e Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test4.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test5.jpg b/tests/wpt/mozilla/tests/mozilla/test5.jpg new file mode 100644 index 00000000000..3df583072e5 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test5.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test6.jpg b/tests/wpt/mozilla/tests/mozilla/test6.jpg new file mode 100644 index 00000000000..81c68f412a2 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test6.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test7.jpg b/tests/wpt/mozilla/tests/mozilla/test7.jpg new file mode 100644 index 00000000000..b6169d4a7b8 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test7.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test8.jpg b/tests/wpt/mozilla/tests/mozilla/test8.jpg new file mode 100644 index 00000000000..295e9c52352 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test8.jpg differ diff --git a/tests/wpt/mozilla/tests/mozilla/test9.jpg b/tests/wpt/mozilla/tests/mozilla/test9.jpg new file mode 100644 index 00000000000..c82f94f0134 Binary files /dev/null and b/tests/wpt/mozilla/tests/mozilla/test9.jpg differ