Bug 1613167 - Enable/Disable rayon in WebRender via pref. r=gw
authorNicolas Silva <nsilva@mozilla.com>
Wed, 05 Feb 2020 09:51:14 +0000
changeset 512556 a9dde10ff4b84810ca804e74caf30e221345d9ed
parent 512555 a290c8f5c4595925a8f843cfc0701a8900a7af90
child 512557 7554cfe059b4078b8b7e3a2d70d1363206cd3a21
push id37092
push userapavel@mozilla.com
push dateWed, 05 Feb 2020 16:27:17 +0000
treeherdermozilla-central@0fa466366383 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1613167
milestone74.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1613167 - Enable/Disable rayon in WebRender via pref. r=gw We need a way to switch it on and off to compare the performance and power usage of various test cases. The new pref is "webrender.enable-multithreading" and does not require a restart. Differential Revision: https://phabricator.services.mozilla.com/D61589
gfx/config/gfxVars.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
gfx/thebes/gfxPlatform.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/src/moz2d_renderer.rs
gfx/wr/examples/blob.rs
gfx/wr/webrender/src/glyph_rasterizer/mod.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/resource_cache.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/webrender_api/src/image.rs
gfx/wr/wrench/src/blob.rs
modules/libpref/init/StaticPrefList.yaml
--- a/gfx/config/gfxVars.h
+++ b/gfx/config/gfxVars.h
@@ -40,16 +40,17 @@ class gfxVarReceiver;
   _(DXP016Blocked, bool, false)                                    \
   _(UseWebRender, bool, false)                                     \
   _(UseWebRenderANGLE, bool, false)                                \
   _(UseWebRenderFlipSequentialWin, bool, false)                    \
   _(UseWebRenderDCompWin, bool, false)                             \
   _(UseWebRenderTripleBufferingWin, bool, false)                   \
   _(UseWebRenderCompositor, bool, false)                           \
   _(UseWebRenderProgramBinaryDisk, bool, false)                    \
+  _(UseWebRenderMultithreading, bool, false)                       \
   _(WebRenderMaxPartialPresentRects, int32_t, 0)                   \
   _(WebRenderDebugFlags, int32_t, 0)                               \
   _(ScreenDepth, int32_t, 0)                                       \
   _(GREDirectory, nsString, nsString())                            \
   _(ProfDirectory, nsString, nsString())                           \
   _(UseOMTP, bool, false)                                          \
   _(AllowD3D11KeyedMutex, bool, false)                             \
   _(SystemTextQuality, int32_t, 5 /* CLEARTYPE_QUALITY */)         \
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2007,16 +2007,18 @@ void CompositorBridgeParent::AccumulateM
     }
   }
 }
 
 /*static*/
 void CompositorBridgeParent::InitializeStatics() {
   gfxVars::SetAllowSacrificingSubpixelAAListener(&UpdateQualitySettings);
   gfxVars::SetWebRenderDebugFlagsListener(&UpdateDebugFlags);
+  gfxVars::SetUseWebRenderMultithreadingListener(
+      &UpdateWebRenderMultithreading);
 }
 
 /*static*/
 void CompositorBridgeParent::UpdateQualitySettings() {
   if (!CompositorThreadHolder::IsInCompositorThread()) {
     if (CompositorLoop()) {
       CompositorLoop()->PostTask(
           NewRunnableFunction("CompositorBridgeParent::UpdateQualitySettings",
@@ -2049,16 +2051,34 @@ void CompositorBridgeParent::UpdateDebug
   }
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void {
     wrBridge->UpdateDebugFlags();
   });
 }
 
+/*static*/
+void CompositorBridgeParent::UpdateWebRenderMultithreading() {
+  if (!CompositorThreadHolder::IsInCompositorThread()) {
+    if (CompositorLoop()) {
+      CompositorLoop()->PostTask(NewRunnableFunction(
+          "CompositorBridgeParent::UpdateWebRenderMultithreading",
+          &CompositorBridgeParent::UpdateWebRenderMultithreading));
+    }
+
+    return;
+  }
+
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void {
+    wrBridge->UpdateMultithreading();
+  });
+}
+
 RefPtr<WebRenderBridgeParent> CompositorBridgeParent::GetWebRenderBridgeParent()
     const {
   return mWrBridge;
 }
 
 Maybe<TimeStamp> CompositorBridgeParent::GetTestingTimeStamp() const {
   return mTestTime;
 }
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -730,16 +730,21 @@ class CompositorBridgeParent final : pub
   static void UpdateQualitySettings();
 
   /**
    * Notify the compositor the debug flags have been updated.
    */
   static void UpdateDebugFlags();
 
   /**
+   * Notify the compositor the debug flags have been updated.
+   */
+  static void UpdateWebRenderMultithreading();
+
+  /**
    * Wrap the data structure to be sent over IPC.
    */
   Maybe<CollectedFramesParams> WrapCollectedFrames(CollectedFrames&& aFrames);
 
  protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~CompositorBridgeParent();
 
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1750,21 +1750,32 @@ void WebRenderBridgeParent::UpdateQualit
     }
     wr::TransactionBuilder txn;
     txn.UpdateQualitySettings(gfxVars::AllowSacrificingSubpixelAA());
     api->SendTransaction(txn);
   }
 }
 
 void WebRenderBridgeParent::UpdateDebugFlags() {
+  auto flags = gfxVars::WebRenderDebugFlags();
   for (auto& api : mApis) {
     if (!api) {
       continue;
     }
-    api->UpdateDebugFlags(gfxVars::WebRenderDebugFlags());
+    api->UpdateDebugFlags(flags);
+  }
+}
+
+void WebRenderBridgeParent::UpdateMultithreading() {
+  bool multithreading = gfxVars::UseWebRenderMultithreading();
+  for (auto& api : mApis) {
+    if (!api) {
+      continue;
+    }
+    api->EnableMultithreading(multithreading);
   }
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
 void WebRenderBridgeParent::RequestScreenPixels(
     UiCompositorControllerParent* aController) {
   mScreenPixelsTarget = aController;
 }
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -100,16 +100,17 @@ class WebRenderBridgeParent final
     return mCompositorScheduler.get();
   }
   CompositorBridgeParentBase* GetCompositorBridge() {
     return mCompositorBridge;
   }
 
   void UpdateQualitySettings();
   void UpdateDebugFlags();
+  void UpdateMultithreading();
 
   mozilla::ipc::IPCResult RecvEnsureConnected(
       TextureFactoryIdentifier* aTextureFactoryIdentifier,
       MaybeIdNamespace* aMaybeIdNamespace) override;
 
   mozilla::ipc::IPCResult RecvNewCompositable(
       const CompositableHandle& aHandle, const TextureInfo& aInfo) override;
   mozilla::ipc::IPCResult RecvReleaseCompositable(
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -610,16 +610,24 @@ static void WebRenderDebugPrefChangeCall
 
   gfx::gfxVars::SetWebRenderDebugFlags(flags.bits);
 }
 
 static void WebRenderQualityPrefChangeCallback(const char* aPref, void*) {
   gfxPlatform::GetPlatform()->UpdateAllowSacrificingSubpixelAA();
 }
 
+static void WebRenderMultithreadingPrefChangeCallback(const char* aPrefName,
+                                                      void*) {
+  bool enable = Preferences::GetBool(
+      StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading(), true);
+
+  gfx::gfxVars::SetUseWebRenderMultithreading(enable);
+}
+
 #if defined(USE_SKIA)
 static uint32_t GetSkiaGlyphCacheSize() {
   // Only increase font cache size on non-android to save memory.
 #  if !defined(MOZ_WIDGET_ANDROID)
   // 10mb as the default pref cache size on desktop due to talos perf tweaking.
   // Chromium uses 20mb and skia default uses 2mb.
   // We don't need to change the font cache count since we usually
   // cache thrash due to asian character sets in talos.
@@ -3329,16 +3337,21 @@ void gfxPlatform::InitWebRenderConfig() 
     if (XRE_IsParentProcess()) {
       Preferences::RegisterPrefixCallbackAndCall(
           WebRenderDebugPrefChangeCallback, WR_DEBUG_PREF);
       Preferences::RegisterCallback(
           WebRenderQualityPrefChangeCallback,
           nsDependentCString(
               StaticPrefs::
                   GetPrefName_gfx_webrender_quality_force_disable_sacrificing_subpixel_aa()));
+      Preferences::RegisterCallback(
+          WebRenderMultithreadingPrefChangeCallback,
+          nsDependentCString(
+              StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading()));
+
       UpdateAllowSacrificingSubpixelAA();
     }
   }
 #if defined(MOZ_WIDGET_GTK)
   else {
     if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
       // Hardware compositing should be disabled by default if we aren't using
       // WebRender. We had to check if it is enabled at all, because it may
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -520,16 +520,20 @@ void WebRenderAPI::Readback(const TimeSt
 }
 
 void WebRenderAPI::ClearAllCaches() { wr_api_clear_all_caches(mDocHandle); }
 
 void WebRenderAPI::EnableNativeCompositor(bool aEnable) {
   wr_api_enable_native_compositor(mDocHandle, aEnable);
 }
 
+void WebRenderAPI::EnableMultithreading(bool aEnable) {
+  wr_api_enable_multithreading(mDocHandle, aEnable);
+}
+
 void WebRenderAPI::Pause() {
   class PauseEvent : public RendererEvent {
    public:
     explicit PauseEvent(layers::SynchronousTask* aTask) : mTask(aTask) {
       MOZ_COUNT_CTOR(PauseEvent);
     }
 
     virtual ~PauseEvent() { MOZ_COUNT_DTOR(PauseEvent); }
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -243,16 +243,17 @@ class WebRenderAPI final {
   void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);
 
   void Readback(const TimeStamp& aStartTime, gfx::IntSize aSize,
                 const gfx::SurfaceFormat& aFormat,
                 const Range<uint8_t>& aBuffer);
 
   void ClearAllCaches();
   void EnableNativeCompositor(bool aEnable);
+  void EnableMultithreading(bool aEnable);
 
   void Pause();
   bool Resume();
 
   void WakeSceneBuilder();
   void FlushSceneBuilder();
 
   void NotifyMemoryPressure();
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1598,16 +1598,21 @@ pub unsafe extern "C" fn wr_api_clear_al
     dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_enable_native_compositor(dh: &mut DocumentHandle, enable: bool) {
     dh.api.send_debug_cmd(DebugCommand::EnableNativeCompositor(enable));
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_enable_multithreading(dh: &mut DocumentHandle, enable: bool) {
+    dh.api.send_debug_cmd(DebugCommand::EnableMultithreading(enable));
+}
+
 fn make_transaction(do_async: bool) -> Transaction {
     let mut transaction = Transaction::new();
     // Ensure that we either use async scene building or not based on the
     // gecko pref, regardless of what the default is. We can remove this once
     // the scene builder thread is enabled everywhere and working well.
     if do_async {
         transaction.use_scene_builder_thread();
     } else {
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -74,16 +74,17 @@ fn dump_index(blob: &[u8]) -> () {
 
 
 
 /// Handles the interpretation and rasterization of gecko-based (moz2d) WR blob images.
 pub struct Moz2dBlobImageHandler {
     workers: Arc<ThreadPool>,
     workers_low_priority: Arc<ThreadPool>,
     blob_commands: HashMap<BlobImageKey, BlobCommand>,
+    enable_multithreading: bool,
 }
 
 /// Transmute some bytes into a value.
 ///
 /// FIXME: kill this with fire and/or do a super robust security audit
 unsafe fn convert_from_bytes<T: Copy>(slice: &[u8]) -> T {
     assert!(mem::size_of::<T>() <= slice.len());
     ptr::read_unaligned(slice.as_ptr() as *const T)
@@ -502,16 +503,18 @@ struct BlobCommand {
 /// Rasterizes gecko blob images.
 struct Moz2dBlobRasterizer {
     /// Pool of rasterizers.
     workers: Arc<ThreadPool>,
     /// Pool of low priority rasterizers.
     workers_low_priority: Arc<ThreadPool>,
     /// Blobs to rasterize.
     blob_commands: HashMap<BlobImageKey, BlobCommand>,
+    ///
+    enable_multithreading: bool,
 }
 
 struct GeckoProfilerMarker {
     name: &'static [u8],
 }
 
 impl GeckoProfilerMarker {
     pub fn new(name: &'static [u8]) -> GeckoProfilerMarker {
@@ -544,17 +547,19 @@ impl AsyncBlobImageRasterizer for Moz2dB
                 visible_rect: command.visible_rect,
                 dirty_rect: params.dirty_rect,
                 tile_size: command.tile_size,
             }
         }).collect();
 
         // If we don't have a lot of blobs it is probably not worth the initial cost
         // of installing work on rayon's thread pool so we do it serially on this thread.
-        let should_parallelize = if low_priority {
+        let should_parallelize = if !self.enable_multithreading {
+            false
+        } else if low_priority {
             requests.len() > 2
         } else {
             // For high priority requests we don't "risk" the potential priority inversion of
             // dispatching to a thread pool full of low priority jobs unless it is really
             // appealing.
             requests.len() > 4
         };
 
@@ -658,16 +663,17 @@ impl BlobImageHandler for Moz2dBlobImage
         self.blob_commands.remove(&key);
     }
 
     fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer> {
         Box::new(Moz2dBlobRasterizer {
             workers: Arc::clone(&self.workers),
             workers_low_priority: Arc::clone(&self.workers_low_priority),
             blob_commands: self.blob_commands.clone(),
+            enable_multithreading: self.enable_multithreading,
         })
     }
 
     fn delete_font(&mut self, font: FontKey) {
         unsafe { DeleteFontData(font); }
     }
 
     fn delete_font_instance(&mut self, key: FontInstanceKey) {
@@ -684,16 +690,20 @@ impl BlobImageHandler for Moz2dBlobImage
         requests: &[BlobImageParams]
     ) {
         for params in requests {
             let commands = &self.blob_commands[&params.request.key];
             let blob = Arc::clone(&commands.data);
             self.prepare_request(&blob, resources);
         }
     }
+
+    fn enable_multithreading(&mut self, enable: bool) {
+        self.enable_multithreading = enable;
+    }
 }
 
 use bindings::{WrFontKey, WrFontInstanceKey, WrIdNamespace};
 
 #[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc<Vec> to an extern function
 extern "C" {
     fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8);
     fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32);
@@ -714,16 +724,17 @@ extern "C" {
 
 impl Moz2dBlobImageHandler {
     /// Create a new BlobImageHandler with the given thread pool.
     pub fn new(workers: Arc<ThreadPool>, workers_low_priority: Arc<ThreadPool>) -> Self {
         Moz2dBlobImageHandler {
             blob_commands: HashMap::new(),
             workers: workers,
             workers_low_priority: workers_low_priority,
+            enable_multithreading: true,
         }
     }
 
     /// Does early preprocessing of a blob's resources.
     ///
     /// Currently just sets up fonts found in the blob.
     fn prepare_request(&self, blob: &[u8], resources: &dyn BlobImageResources) {
         #[cfg(target_os = "windows")]
--- a/gfx/wr/examples/blob.rs
+++ b/gfx/wr/examples/blob.rs
@@ -155,16 +155,17 @@ impl api::BlobImageHandler for Checkerbo
     }
 
     fn prepare_resources(
         &mut self,
         _services: &dyn api::BlobImageResources,
         _requests: &[api::BlobImageParams],
     ) {}
 
+    fn enable_multithreading(&mut self, _: bool) {}
     fn delete_font(&mut self, _font: api::FontKey) {}
     fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) {}
     fn clear_namespace(&mut self, _namespace: api::IdNamespace) {}
     fn create_blob_rasterizer(&mut self) -> Box<dyn api::AsyncBlobImageRasterizer> {
         Box::new(Rasterizer {
             workers: Arc::clone(&self.workers),
             image_cmds: self.image_cmds.clone(),
         })
--- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
@@ -103,16 +103,20 @@ impl GlyphRasterizer {
             return;
         }
 
         self.pending_glyphs += 1;
 
         self.request_glyphs_from_backend(font, new_glyphs);
     }
 
+    pub fn enable_multithreading(&mut self, enable: bool) {
+        self.enable_multithreading = enable;
+    }
+
     pub(in super) fn request_glyphs_from_backend(&mut self, font: FontInstance, glyphs: Vec<GlyphKey>) {
         let font_contexts = Arc::clone(&self.font_contexts);
         let glyph_tx = self.glyph_tx.clone();
 
         fn process_glyph(key: &GlyphKey, font_contexts: &FontContexts, font: &FontInstance) -> GlyphRasterJob {
             profile_scope!("glyph-raster");
             let mut context = font_contexts.lock_current_context();
             let mut job = GlyphRasterJob {
@@ -149,17 +153,17 @@ impl GlyphRasterizer {
                 glyph.downscale_bitmap_if_required(&font);
             }
 
             job
         }
 
         // if the number of glyphs is small, do it inline to avoid the threading overhead;
         // send the result into glyph_tx so downstream code can't tell the difference.
-        if glyphs.len() < 8 {
+        if !self.enable_multithreading || glyphs.len() < 8 {
             let jobs = glyphs.iter()
                              .map(|key: &GlyphKey| process_glyph(key, &font_contexts, &font))
                              .collect();
             glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
         } else {
             // spawn an async task to get off of the render backend thread as early as
             // possible and in that task use rayon's fork join dispatch to rasterize the
             // glyphs in the thread pool.
@@ -875,16 +879,19 @@ pub struct GlyphRasterizer {
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
     // Defer removal of font instances, as for fonts.
     font_instances_to_remove: Vec<FontInstance>,
 
     #[allow(dead_code)]
     next_gpu_glyph_cache_key: GpuGlyphCacheKey,
+
+    // Whether to parallelize glyph rasterization with rayon.
+    enable_multithreading: bool,
 }
 
 impl GlyphRasterizer {
     pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         let (glyph_tx, glyph_rx) = channel();
 
         let num_workers = workers.current_num_threads();
         let mut contexts = Vec::with_capacity(num_workers);
@@ -907,16 +914,17 @@ impl GlyphRasterizer {
             font_contexts: Arc::new(font_context),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
             font_instances_to_remove: Vec::new(),
             next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
+            enable_multithreading: true,
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         self.font_contexts.async_for_each(move |mut context| {
             context.add_font(&font_key, &template);
         });
     }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1249,16 +1249,20 @@ impl RenderBackend {
                         // Update config in SceneBuilder
                         self.low_priority_scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig(
                             self.frame_config.clone()
                          )).unwrap();
 
                         // We don't want to forward this message to the renderer.
                         return RenderBackendStatus::Continue;
                     }
+                    DebugCommand::EnableMultithreading(enable) => {
+                        self.resource_cache.enable_multithreading(enable);
+                        return RenderBackendStatus::Continue;
+                    }
                     DebugCommand::SimulateLongSceneBuild(time_ms) => {
                         self.scene_tx.send(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)).unwrap();
                         return RenderBackendStatus::Continue;
                     }
                     DebugCommand::SimulateLongLowPrioritySceneBuild(time_ms) => {
                         self.low_priority_scene_tx.send(
                             SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)
                         ).unwrap();
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -2280,16 +2280,17 @@ impl Renderer {
                 }
             })?;
 
             low_priority_scene_tx
         } else {
             scene_tx.clone()
         };
 
+        let enable_multithreading = options.enable_multithreading;
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_started(&rb_thread_name);
             }
 
             let texture_cache = TextureCache::new(
                 max_texture_size,
@@ -2301,23 +2302,25 @@ impl Renderer {
                 },
                 start_size,
                 color_cache_formats,
                 swizzle_settings,
             );
 
             let glyph_cache = GlyphCache::new(max_glyph_cache_size);
 
-            let resource_cache = ResourceCache::new(
+            let mut resource_cache = ResourceCache::new(
                 texture_cache,
                 glyph_rasterizer,
                 glyph_cache,
                 blob_image_handler,
             );
 
+            resource_cache.enable_multithreading(enable_multithreading);
+
             let mut backend = RenderBackend::new(
                 api_rx,
                 payload_rx_for_backend,
                 result_tx,
                 scene_tx,
                 low_priority_scene_tx,
                 scene_rx,
                 device_pixel_ratio,
@@ -2869,17 +2872,18 @@ impl Renderer {
             }
             DebugCommand::SaveCapture(..) |
             DebugCommand::LoadCapture(..) => {
                 panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
             }
             DebugCommand::ClearCaches(_)
             | DebugCommand::SimulateLongSceneBuild(_)
             | DebugCommand::SimulateLongLowPrioritySceneBuild(_)
-            | DebugCommand::EnableNativeCompositor(_) => {}
+            | DebugCommand::EnableNativeCompositor(_)
+            | DebugCommand::EnableMultithreading(_) => {}
             DebugCommand::InvalidateGpuCache => {
                 match self.gpu_cache_texture.bus {
                     GpuCacheBus::PixelBuffer { ref mut rows, .. } => {
                         info!("Invalidating GPU caches");
                         for row in rows {
                             row.is_dirty = true;
                         }
                     }
@@ -6217,16 +6221,17 @@ pub struct RendererOptions {
     pub force_subpixel_aa: bool,
     pub clear_color: Option<ColorF>,
     pub enable_clear_scissor: bool,
     pub max_texture_size: Option<i32>,
     pub max_glyph_cache_size: Option<usize>,
     pub scatter_gpu_cache_updates: bool,
     pub upload_method: UploadMethod,
     pub workers: Option<Arc<ThreadPool>>,
+    pub enable_multithreading: bool,
     pub blob_image_handler: Option<Box<dyn BlobImageHandler>>,
     pub recorder: Option<Box<dyn ApiRecordingReceiver>>,
     pub thread_listener: Option<Box<dyn ThreadListener + Send + Sync>>,
     pub size_of_op: Option<VoidPtrToSizeFn>,
     pub enclosing_size_of_op: Option<VoidPtrToSizeFn>,
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
@@ -6290,16 +6295,17 @@ impl Default for RendererOptions {
             max_texture_size: None,
             max_glyph_cache_size: None,
             // Scattered GPU cache updates haven't met a test that would show their superiority yet.
             scatter_gpu_cache_updates: false,
             // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL,
             // but we are unable to make this decision here, so picking the reasonable medium.
             upload_method: UploadMethod::PixelBuffer(VertexUsageHint::Stream),
             workers: None,
+            enable_multithreading: true,
             blob_image_handler: None,
             recorder: None,
             thread_listener: None,
             size_of_op: None,
             enclosing_size_of_op: None,
             renderer_id: None,
             cached_programs: None,
             scene_builder_hooks: None,
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -551,16 +551,23 @@ impl ResourceCache {
             pending_native_surface_updates: Vec::new(),
         }
     }
 
     pub fn max_texture_size(&self) -> i32 {
         self.texture_cache.max_texture_size()
     }
 
+    pub fn enable_multithreading(&mut self, enable: bool) {
+        self.glyph_rasterizer.enable_multithreading(enable);
+        if let Some(ref mut handler) = self.blob_image_handler {
+            handler.enable_multithreading(enable);
+        }
+    }
+
     fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool {
         let size_check = descriptor.size.width > limit || descriptor.size.height > limit;
         match *data {
             CachedImageData::Raw(_) | CachedImageData::Blob => size_check,
             CachedImageData::External(info) => {
                 // External handles already represent existing textures so it does
                 // not make sense to tile them into smaller ones.
                 info.image_type == ExternalImageType::Buffer && size_check
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -967,16 +967,18 @@ pub enum DebugCommand {
     /// Save a capture of all the documents state.
     SaveCapture(PathBuf, CaptureBits),
     /// Load a capture of all the documents state.
     LoadCapture(PathBuf, MsgSender<CapturedDocument>),
     /// Clear cached resources, forcing them to be re-uploaded from templates.
     ClearCaches(ClearCache),
     /// Enable/disable native compositor usage
     EnableNativeCompositor(bool),
+    /// Enable/disable parallel job execution with rayon.
+    EnableMultithreading(bool),
     /// Invalidate GPU cache, forcing the update from the CPU mirror.
     InvalidateGpuCache,
     /// Causes the scene builder to pause for a given amount of milliseconds each time it
     /// processes a transaction.
     SimulateLongSceneBuild(u32),
     /// Causes the low priority scene builder to pause for a given amount of milliseconds
     /// each time it processes a transaction.
     SimulateLongLowPrioritySceneBuild(u32),
--- a/gfx/wr/webrender_api/src/image.rs
+++ b/gfx/wr/webrender_api/src/image.rs
@@ -404,16 +404,19 @@ pub trait BlobImageHandler: Send {
 
     /// A hook to let the handler clean up any state related to a font instance which the
     /// resource cache is about to delete.
     fn delete_font_instance(&mut self, key: FontInstanceKey);
 
     /// A hook to let the handler clean up any state related a given namespace before the
     /// resource cache deletes them.
     fn clear_namespace(&mut self, namespace: IdNamespace);
+
+    /// Whether to allow rendering blobs on multiple threads.
+    fn enable_multithreading(&mut self, enable: bool);
 }
 
 /// A group of rasterization requests to execute synchronously on the scene builder thread.
 pub trait AsyncBlobImageRasterizer : Send {
     /// Rasterize the requests.
     ///
     /// Gecko uses te priority hint to schedule work in a way that minimizes the risk
     /// of high priority work being blocked by (or enqued behind) low priority work.
--- a/gfx/wr/wrench/src/blob.rs
+++ b/gfx/wr/wrench/src/blob.rs
@@ -161,16 +161,18 @@ impl BlobImageHandler for CheckerboardRe
         if !requests.is_empty() {
             (self.callbacks.lock().unwrap().request)(&requests);
         }
     }
 
     fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer> {
         Box::new(Rasterizer { image_cmds: self.image_cmds.clone() })
     }
+
+    fn enable_multithreading(&mut self, _enable: bool) {}
 }
 
 struct Command {
     request: BlobImageRequest,
     color: ColorU,
     descriptor: BlobImageDescriptor,
     tile: Option<(TileSize, TileOffset)>,
     dirty_rect: BlobDirtyRect,
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -3841,16 +3841,21 @@
   value: true
   mirror: always
 
 - name: gfx.webrender.split-render-roots
   type: bool
   value: false
   mirror: once
 
+- name: gfx.webrender.enable-multithreading
+  type: bool
+  value: true
+  mirror: always
+
 - name: gfx.webrender.compositor
   type: bool
 #if defined(XP_WIN) || defined(XP_MACOSX)
   value: true
 #else
   value: false
 #endif
   mirror: once