From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andy Dill <andy.dill@gmail.com>
Date: Thu, 30 Jan 2020 09:36:07 -0800
Subject: feat: enable off-screen rendering with viz compositor

This patch adds hooks in the relevant places that allow for off-screen
rendering with the viz compositor by way of a custom HostDisplayClient
and LayeredWindowUpdater.

diff --git a/components/viz/host/host_display_client.cc b/components/viz/host/host_display_client.cc
index 7af928a9a030c0bc1efe069f0a4004adc5ae9c14..e1b04e1e2f76a3d9ba4575844e1ae49dd909ecb2 100644
--- a/components/viz/host/host_display_client.cc
+++ b/components/viz/host/host_display_client.cc
@@ -47,9 +47,9 @@ void HostDisplayClient::OnDisplayReceivedCALayerParams(
 }
 #endif
 
-#if BUILDFLAG(IS_WIN)
 void HostDisplayClient::CreateLayeredWindowUpdater(
     mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) {
+#if BUILDFLAG(IS_WIN)
   if (!NeedsToUseLayerWindow(widget_)) {
     DLOG(ERROR) << "HWND shouldn't be using a layered window";
     return;
@@ -57,7 +57,15 @@ void HostDisplayClient::CreateLayeredWindowUpdater(
 
   layered_window_updater_ =
       std::make_unique<LayeredWindowUpdaterImpl>(widget_, std::move(receiver));
+
+#else
+  CHECK(false) << "Chromium is calling CreateLayeredWindowUpdater for non-OSR "
+                  "windows on POSIX platforms, something is wrong with "
+                  "Electron's OSR implementation.";
+#endif
 }
+
+#if BUILDFLAG(IS_WIN)
 void HostDisplayClient::AddChildWindowToBrowser(
     gpu::SurfaceHandle child_window) {
   NOTREACHED();
diff --git a/components/viz/host/host_display_client.h b/components/viz/host/host_display_client.h
index cb16487f9fc0811ef577d455098f09e3000db86a..d67c0f8bce3c6a8f7643409e07e21e789b4bd391 100644
--- a/components/viz/host/host_display_client.h
+++ b/components/viz/host/host_display_client.h
@@ -39,6 +39,9 @@ class VIZ_HOST_EXPORT HostDisplayClient : public mojom::DisplayClient {
   gfx::AcceleratedWidget widget() const { return widget_; }
 #endif
 
+  void CreateLayeredWindowUpdater(
+      mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) override;
+
  private:
   // mojom::DisplayClient implementation:
 #if BUILDFLAG(IS_APPLE)
@@ -47,8 +50,6 @@ class VIZ_HOST_EXPORT HostDisplayClient : public mojom::DisplayClient {
 #endif
 
 #if BUILDFLAG(IS_WIN)
-  void CreateLayeredWindowUpdater(
-      mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) override;
   void AddChildWindowToBrowser(gpu::SurfaceHandle child_window) override;
 #endif
 
diff --git a/components/viz/host/layered_window_updater_impl.cc b/components/viz/host/layered_window_updater_impl.cc
index 8f726bde2cb5da6acfe630006af0fc3a09811d6c..45d8cae3ba0fca9a1514f83032a10c8820b3126d 100644
--- a/components/viz/host/layered_window_updater_impl.cc
+++ b/components/viz/host/layered_window_updater_impl.cc
@@ -46,7 +46,9 @@ void LayeredWindowUpdaterImpl::OnAllocatedSharedMemory(
   // |region|'s handle will close when it goes out of scope.
 }
 
-void LayeredWindowUpdaterImpl::Draw(DrawCallback draw_callback) {
+void LayeredWindowUpdaterImpl::Draw(
+    const gfx::Rect& damage_rect,
+    DrawCallback draw_callback) {
   TRACE_EVENT0("viz", "LayeredWindowUpdaterImpl::Draw");
 
   if (!canvas_) {
diff --git a/components/viz/host/layered_window_updater_impl.h b/components/viz/host/layered_window_updater_impl.h
index 8af69cac78b7488d28f1f05ccb174793fe5148cd..9f74e511c263d147b5fbe81fe100d217eb0b64c9 100644
--- a/components/viz/host/layered_window_updater_impl.h
+++ b/components/viz/host/layered_window_updater_impl.h
@@ -38,7 +38,7 @@ class VIZ_HOST_EXPORT LayeredWindowUpdaterImpl
   // mojom::LayeredWindowUpdater implementation.
   void OnAllocatedSharedMemory(const gfx::Size& pixel_size,
                                base::UnsafeSharedMemoryRegion region) override;
-  void Draw(DrawCallback draw_callback) override;
+  void Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) override;
 
  private:
   const HWND hwnd_;
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 17d4b2f0490894058f1896b694a558d387f25993..caf00099657226b1f89eeb243897c7770cc20920 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -165,6 +165,8 @@ viz_component("service") {
     "display_embedder/skia_output_surface_impl_on_gpu_debug_capture.h",
     "display_embedder/skia_render_copy_results.cc",
     "display_embedder/skia_render_copy_results.h",
+    "display_embedder/software_output_device_proxy.cc",
+    "display_embedder/software_output_device_proxy.h",
     "display_embedder/software_output_surface.cc",
     "display_embedder/software_output_surface.h",
     "display_embedder/vsync_parameter_listener.cc",
diff --git a/components/viz/service/display_embedder/output_surface_provider.h b/components/viz/service/display_embedder/output_surface_provider.h
index a4e6ad16fd2def9bbed7cd11ea13ba365a9f7322..20a426e6f7dff17c22dd05d75d601a560cd34283 100644
--- a/components/viz/service/display_embedder/output_surface_provider.h
+++ b/components/viz/service/display_embedder/output_surface_provider.h
@@ -38,7 +38,8 @@ class OutputSurfaceProvider {
       mojom::DisplayClient* display_client,
       DisplayCompositorMemoryAndTaskController* gpu_dependency,
       const RendererSettings& renderer_settings,
-      const DebugRendererSettings* debug_settings) = 0;
+      const DebugRendererSettings* debug_settings,
+      bool offscreen) = 0;
 
   virtual gpu::SharedImageManager* GetSharedImageManager() = 0;
   virtual gpu::SyncPointManager* GetSyncPointManager() = 0;
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc
index e8e3237bb2b8f134be81441ada743ad78f147d97..a5c5360377d9d5f6e46b0ff023ca302128bfb327 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc
@@ -25,12 +25,14 @@
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
+#include "components/viz/service/display_embedder/software_output_device_proxy.h"
 #include "components/viz/service/display_embedder/software_output_surface.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
 #include "gpu/command_buffer/client/shared_memory_limits.h"
 #include "gpu/command_buffer/service/scheduler_sequence.h"
 #include "gpu/config/gpu_finch_features.h"
 #include "gpu/ipc/common/surface_handle.h"
+#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
 #include "ui/base/ui_base_switches.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -94,7 +96,8 @@ std::unique_ptr<OutputSurface> OutputSurfaceProviderImpl::CreateOutputSurface(
     mojom::DisplayClient* display_client,
     DisplayCompositorMemoryAndTaskController* gpu_dependency,
     const RendererSettings& renderer_settings,
-    const DebugRendererSettings* debug_settings) {
+    const DebugRendererSettings* debug_settings,
+    bool offscreen) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (surface_handle == gpu::kNullSurfaceHandle)
     return std::make_unique<OutputSurfaceUnified>();
@@ -102,7 +105,7 @@ std::unique_ptr<OutputSurface> OutputSurfaceProviderImpl::CreateOutputSurface(
 
   if (!gpu_compositing) {
     return std::make_unique<SoftwareOutputSurface>(
-        CreateSoftwareOutputDeviceForPlatform(surface_handle, display_client));
+        CreateSoftwareOutputDeviceForPlatform(surface_handle, display_client, offscreen));
   } else {
     DCHECK(gpu_dependency);
 
@@ -142,10 +145,22 @@ std::unique_ptr<OutputSurface> OutputSurfaceProviderImpl::CreateOutputSurface(
 std::unique_ptr<SoftwareOutputDevice>
 OutputSurfaceProviderImpl::CreateSoftwareOutputDeviceForPlatform(
     gpu::SurfaceHandle surface_handle,
-    mojom::DisplayClient* display_client) {
+    mojom::DisplayClient* display_client,
+    bool offscreen) {
   if (headless_)
     return std::make_unique<SoftwareOutputDevice>();
 
+#if !BUILDFLAG(IS_APPLE)
+  if (offscreen) {
+    DCHECK(display_client);
+    mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater;
+    display_client->CreateLayeredWindowUpdater(
+        layered_window_updater.InitWithNewPipeAndPassReceiver());
+    return std::make_unique<SoftwareOutputDeviceProxy>(
+        std::move(layered_window_updater));
+  }
+#endif
+
 #if BUILDFLAG(IS_WIN)
   return CreateSoftwareOutputDeviceWin(surface_handle, &output_device_backing_,
                                        display_client);
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.h b/components/viz/service/display_embedder/output_surface_provider_impl.h
index d149ef23deaf591e472fcd00a7ea32b2e4052b98..6e93321d2856d40a00fa04fe03a2bb72ba650e83 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.h
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.h
@@ -50,7 +50,8 @@ class VIZ_SERVICE_EXPORT OutputSurfaceProviderImpl
       mojom::DisplayClient* display_client,
       DisplayCompositorMemoryAndTaskController* gpu_dependency,
       const RendererSettings& renderer_settings,
-      const DebugRendererSettings* debug_settings) override;
+      const DebugRendererSettings* debug_settings,
+      bool offscreen) override;
 
   gpu::SharedImageManager* GetSharedImageManager() override;
   gpu::SyncPointManager* GetSyncPointManager() override;
@@ -58,7 +59,8 @@ class VIZ_SERVICE_EXPORT OutputSurfaceProviderImpl
  private:
   std::unique_ptr<SoftwareOutputDevice> CreateSoftwareOutputDeviceForPlatform(
       gpu::SurfaceHandle surface_handle,
-      mojom::DisplayClient* display_client);
+      mojom::DisplayClient* display_client,
+      bool offscreen);
 
   const raw_ptr<GpuServiceImpl> gpu_service_impl_;
 
diff --git a/components/viz/service/display_embedder/software_output_device_mac.cc b/components/viz/service/display_embedder/software_output_device_mac.cc
index 5dccc2360cd1f3d83ffc59697aeb559a19b0547a..5fe62069b15e6370e63645b257d931be2a714bc3 100644
--- a/components/viz/service/display_embedder/software_output_device_mac.cc
+++ b/components/viz/service/display_embedder/software_output_device_mac.cc
@@ -106,6 +106,8 @@ void SoftwareOutputDeviceMac::UpdateAndCopyBufferDamage(
 
 SkCanvas* SoftwareOutputDeviceMac::BeginPaint(
     const gfx::Rect& new_damage_rect) {
+  last_damage = new_damage_rect;
+
   // Record the previous paint buffer.
   Buffer* previous_paint_buffer =
       buffer_queue_.empty() ? nullptr : buffer_queue_.back().get();
@@ -194,6 +196,7 @@ void SoftwareOutputDeviceMac::EndPaint() {
     ca_layer_params.is_empty = false;
     ca_layer_params.scale_factor = scale_factor_;
     ca_layer_params.pixel_size = pixel_size_;
+    ca_layer_params.damage = last_damage;
     ca_layer_params.io_surface_mach_port.reset(
         IOSurfaceCreateMachPort(current_paint_buffer_->io_surface.get()));
     client_->SoftwareDeviceUpdatedCALayerParams(ca_layer_params);
diff --git a/components/viz/service/display_embedder/software_output_device_mac.h b/components/viz/service/display_embedder/software_output_device_mac.h
index 67d5ff67d74c107a867b39b306c6528425b87e05..5fd12a25c9e319e8e675955926271c9d1cd3a7ca 100644
--- a/components/viz/service/display_embedder/software_output_device_mac.h
+++ b/components/viz/service/display_embedder/software_output_device_mac.h
@@ -62,6 +62,7 @@ class VIZ_SERVICE_EXPORT SoftwareOutputDeviceMac : public SoftwareOutputDevice {
   void UpdateAndCopyBufferDamage(Buffer* previous_paint_buffer,
                                  const SkRegion& new_damage_rect);
 
+  gfx::Rect last_damage;
   gfx::Size pixel_size_;
   float scale_factor_ = 1;
 
diff --git a/components/viz/service/display_embedder/software_output_device_proxy.cc b/components/viz/service/display_embedder/software_output_device_proxy.cc
new file mode 100644
index 0000000000000000000000000000000000000000..25c427e337a1bc049cd6977a13c4113085dc9401
--- /dev/null
+++ b/components/viz/service/display_embedder/software_output_device_proxy.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/viz/service/display_embedder/software_output_device_proxy.h"
+
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/threading/thread_checker.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "components/viz/common/resources/resource_sizes.h"
+#include "components/viz/service/display_embedder/output_device_backing.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/skia_util.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "skia/ext/skia_utils_win.h"
+#include "ui/gfx/gdi_util.h"
+#include "ui/gfx/win/hwnd_util.h"
+#else
+#include "mojo/public/cpp/base/shared_memory_utils.h"
+#endif
+
+namespace viz {
+
+SoftwareOutputDeviceBase::~SoftwareOutputDeviceBase() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!in_paint_);
+}
+
+void SoftwareOutputDeviceBase::Resize(const gfx::Size& viewport_pixel_size,
+                                      float scale_factor) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!in_paint_);
+
+  if (viewport_pixel_size_ == viewport_pixel_size)
+    return;
+
+  viewport_pixel_size_ = viewport_pixel_size;
+  ResizeDelegated();
+}
+
+SkCanvas* SoftwareOutputDeviceBase::BeginPaint(const gfx::Rect& damage_rect) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!in_paint_);
+
+  damage_rect_ = damage_rect;
+  in_paint_ = true;
+  return BeginPaintDelegated();
+}
+
+void SoftwareOutputDeviceBase::EndPaint() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(in_paint_);
+
+  in_paint_ = false;
+
+  gfx::Rect intersected_damage_rect = damage_rect_;
+  intersected_damage_rect.Intersect(gfx::Rect(viewport_pixel_size_));
+  if (intersected_damage_rect.IsEmpty())
+    return;
+
+  EndPaintDelegated(intersected_damage_rect);
+}
+
+SoftwareOutputDeviceProxy::~SoftwareOutputDeviceProxy() = default;
+
+SoftwareOutputDeviceProxy::SoftwareOutputDeviceProxy(
+    mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater)
+    : layered_window_updater_(std::move(layered_window_updater)) {
+  DCHECK(layered_window_updater_.is_bound());
+}
+
+void SoftwareOutputDeviceProxy::OnSwapBuffers(
+    SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback,
+    gfx::FrameData data) {
+  DCHECK(swap_ack_callback_.is_null());
+
+  // We aren't waiting on DrawAck() and can immediately run the callback.
+  if (!waiting_on_draw_ack_) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_));
+    return;
+  }
+
+  swap_ack_callback_ = std::move(swap_ack_callback);
+}
+
+void SoftwareOutputDeviceProxy::ResizeDelegated() {
+  canvas_.reset();
+
+  size_t required_bytes;
+  if (!ResourceSizes::MaybeSizeInBytes(viewport_pixel_size_,
+                                       SinglePlaneFormat::kRGBA_8888,
+                                       &required_bytes)) {
+    DLOG(ERROR) << "Invalid viewport size " << viewport_pixel_size_.ToString();
+    return;
+  }
+
+  base::UnsafeSharedMemoryRegion region =
+      base::UnsafeSharedMemoryRegion::Create(required_bytes);
+  if (!region.IsValid()) {
+    DLOG(ERROR) << "Failed to allocate " << required_bytes << " bytes";
+    return;
+  }
+
+#if defined(WIN32)
+  canvas_ = skia::CreatePlatformCanvasWithSharedSection(
+      viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,
+      region.GetPlatformHandle(), skia::CRASH_ON_FAILURE);
+#else
+  shm_mapping_ = region.Map();
+  if (!shm_mapping_.IsValid()) {
+    DLOG(ERROR) << "Failed to map " << required_bytes << " bytes";
+    return;
+  }
+
+  canvas_ = skia::CreatePlatformCanvasWithPixels(
+      viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,
+      static_cast<uint8_t*>(shm_mapping_.memory()), skia::CRASH_ON_FAILURE);
+#endif
+
+  // Transfer region ownership to the browser process.
+  layered_window_updater_->OnAllocatedSharedMemory(viewport_pixel_size_,
+                                                   std::move(region));
+}
+
+SkCanvas* SoftwareOutputDeviceProxy::BeginPaintDelegated() {
+  return canvas_.get();
+}
+
+void SoftwareOutputDeviceProxy::EndPaintDelegated(
+    const gfx::Rect& damage_rect) {
+  DCHECK(!waiting_on_draw_ack_);
+
+  if (!canvas_)
+    return;
+
+  layered_window_updater_->Draw(
+      damage_rect, base::BindOnce(&SoftwareOutputDeviceProxy::DrawAck,
+                                  base::Unretained(this)));
+  waiting_on_draw_ack_ = true;
+
+  TRACE_EVENT_ASYNC_BEGIN0("viz", "SoftwareOutputDeviceProxy::Draw", this);
+}
+
+void SoftwareOutputDeviceProxy::DrawAck() {
+  DCHECK(waiting_on_draw_ack_);
+  DCHECK(!swap_ack_callback_.is_null());
+
+  TRACE_EVENT_ASYNC_END0("viz", "SoftwareOutputDeviceProxy::Draw", this);
+
+  waiting_on_draw_ack_ = false;
+  std::move(swap_ack_callback_).Run(viewport_pixel_size_);
+}
+
+}  // namespace viz
diff --git a/components/viz/service/display_embedder/software_output_device_proxy.h b/components/viz/service/display_embedder/software_output_device_proxy.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1a22ee881c0fd679ac2d2d4d11a3c937cc4e9d1
--- /dev/null
+++ b/components/viz/service/display_embedder/software_output_device_proxy.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_
+
+#include <memory>
+
+#include "base/memory/shared_memory_mapping.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "components/viz/host/host_display_client.h"
+#include "components/viz/service/display/software_output_device.h"
+#include "components/viz/service/viz_service_export.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/viz/privileged/mojom/compositing/display_private.mojom.h"
+#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+#endif
+
+namespace viz {
+
+// Shared base class for SoftwareOutputDevice implementations.
+class SoftwareOutputDeviceBase : public SoftwareOutputDevice {
+ public:
+  SoftwareOutputDeviceBase() = default;
+  ~SoftwareOutputDeviceBase() override;
+
+  SoftwareOutputDeviceBase(const SoftwareOutputDeviceBase&) = delete;
+  SoftwareOutputDeviceBase& operator=(const SoftwareOutputDeviceBase&) = delete;
+
+  // SoftwareOutputDevice implementation.
+  void Resize(const gfx::Size& viewport_pixel_size,
+              float scale_factor) override;
+  SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override;
+  void EndPaint() override;
+
+  // Called from Resize() if |viewport_pixel_size_| has changed.
+  virtual void ResizeDelegated() = 0;
+
+  // Called from BeginPaint() and should return an SkCanvas.
+  virtual SkCanvas* BeginPaintDelegated() = 0;
+
+  // Called from EndPaint() if there is damage.
+  virtual void EndPaintDelegated(const gfx::Rect& damage_rect) = 0;
+
+ private:
+  bool in_paint_ = false;
+
+  THREAD_CHECKER(thread_checker_);
+};
+
+// SoftwareOutputDevice implementation that draws indirectly. An implementation
+// of mojom::LayeredWindowUpdater in the browser process handles the actual
+// drawing. Pixel backing is in SharedMemory so no copying between processes
+// is required.
+class SoftwareOutputDeviceProxy : public SoftwareOutputDeviceBase {
+ public:
+  explicit SoftwareOutputDeviceProxy(
+      mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater);
+  ~SoftwareOutputDeviceProxy() override;
+
+  SoftwareOutputDeviceProxy(const SoftwareOutputDeviceProxy&) = delete;
+  SoftwareOutputDeviceProxy& operator=(const SoftwareOutputDeviceProxy&) =
+      delete;
+
+  // SoftwareOutputDevice implementation.
+  void OnSwapBuffers(
+      SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback,
+      gfx::FrameData data) override;
+
+  // SoftwareOutputDeviceBase implementation.
+  void ResizeDelegated() override;
+  SkCanvas* BeginPaintDelegated() override;
+  void EndPaintDelegated(const gfx::Rect& rect) override;
+
+ private:
+  // Runs |swap_ack_callback_| after draw has happened.
+  void DrawAck();
+
+  mojo::Remote<mojom::LayeredWindowUpdater> layered_window_updater_;
+
+  std::unique_ptr<SkCanvas> canvas_;
+  bool waiting_on_draw_ack_ = false;
+  SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback_;
+
+#if !defined(WIN32)
+  base::WritableSharedMemoryMapping shm_mapping_;
+#endif
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_
diff --git a/components/viz/service/display_embedder/software_output_device_win.cc b/components/viz/service/display_embedder/software_output_device_win.cc
index 796ae2688436eb07f19909641d1620dd02f10cdb..c9e0eee0b329caf46669b419b1cd10cf2a707ac8 100644
--- a/components/viz/service/display_embedder/software_output_device_win.cc
+++ b/components/viz/service/display_embedder/software_output_device_win.cc
@@ -193,7 +193,7 @@ void SoftwareOutputDeviceWinProxy::EndPaintDelegated(
   if (!canvas_)
     return;
 
-  layered_window_updater_->Draw(base::BindOnce(
+  layered_window_updater_->Draw(damage_rect, base::BindOnce(
       &SoftwareOutputDeviceWinProxy::DrawAck, base::Unretained(this)));
   waiting_on_draw_ack_ = true;
 
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index f8e148cb3b61f398db71fbb2933f9f264567508a..b9fd9314a2c27bd08c341171d6abef0bfb1d946c 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -100,7 +100,8 @@ RootCompositorFrameSinkImpl::Create(
       params->gpu_compositing, params->widget);
   auto output_surface = output_surface_provider->CreateOutputSurface(
       params->widget, params->gpu_compositing, display_client.get(),
-      display_controller.get(), params->renderer_settings, debug_settings);
+      display_controller.get(), params->renderer_settings, debug_settings,
+      params->offscreen);
 
   // Creating output surface failed. The host can send a new request, possibly
   // with a different compositing mode.
diff --git a/components/viz/test/test_output_surface_provider.cc b/components/viz/test/test_output_surface_provider.cc
index 7648cb06aa78891804bb0783f884b09e07597fbc..f1a73f157ca43620019b864aef35d0ad36c12353 100644
--- a/components/viz/test/test_output_surface_provider.cc
+++ b/components/viz/test/test_output_surface_provider.cc
@@ -30,7 +30,8 @@ std::unique_ptr<OutputSurface> TestOutputSurfaceProvider::CreateOutputSurface(
     mojom::DisplayClient* display_client,
     DisplayCompositorMemoryAndTaskController* display_controller,
     const RendererSettings& renderer_settings,
-    const DebugRendererSettings* debug_settings) {
+    const DebugRendererSettings* debug_settings,
+    bool offscreen) {
   if (gpu_compositing) {
     return FakeSkiaOutputSurface::Create3d();
   } else {
diff --git a/components/viz/test/test_output_surface_provider.h b/components/viz/test/test_output_surface_provider.h
index febb2718cb34ea4d9f411f068d8c01a89c7db888..be8bd51cb61c20ef3df8552972a0ac2f11930150 100644
--- a/components/viz/test/test_output_surface_provider.h
+++ b/components/viz/test/test_output_surface_provider.h
@@ -32,7 +32,8 @@ class TestOutputSurfaceProvider : public OutputSurfaceProvider {
       mojom::DisplayClient* display_client,
       DisplayCompositorMemoryAndTaskController* display_controller,
       const RendererSettings& renderer_settings,
-      const DebugRendererSettings* debug_settings) override;
+      const DebugRendererSettings* debug_settings,
+      bool offscreen) override;
   gpu::SharedImageManager* GetSharedImageManager() override;
   gpu::SyncPointManager* GetSyncPointManager() override;
 };
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index 3634618a687b375c2b0af7922c2f70347fdc0309..cdbb787d219578e9371333f699cfd25859c483fb 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -385,8 +385,14 @@ void VizProcessTransportFactory::OnEstablishedGpuChannel(
   mojo::AssociatedRemote<viz::mojom::DisplayPrivate> display_private;
   root_params->display_private =
       display_private.BindNewEndpointAndPassReceiver();
-  compositor_data.display_client =
-      std::make_unique<HostDisplayClient>(compositor);
+  if (compositor->delegate()) {
+    compositor_data.display_client = compositor->delegate()->CreateHostDisplayClient(
+        compositor);
+    root_params->offscreen = compositor->delegate()->IsOffscreen();
+  } else {
+    compositor_data.display_client =
+        std::make_unique<HostDisplayClient>(compositor);
+  }
   root_params->display_client =
       compositor_data.display_client->GetBoundRemote(resize_task_runner_);
   mojo::AssociatedRemote<viz::mojom::ExternalBeginFrameController>
diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom
index d7deccb6e6ec63592cd840a05403f402238e645e..4c4356b8def15ed3156db38d0a593b83a0544ed4 100644
--- a/services/viz/privileged/mojom/compositing/display_private.mojom
+++ b/services/viz/privileged/mojom/compositing/display_private.mojom
@@ -114,7 +114,6 @@ interface DisplayClient {
 
   // Creates a LayeredWindowUpdater implementation to draw into a layered
   // window.
-  [EnableIf=is_win]
   CreateLayeredWindowUpdater(pending_receiver<LayeredWindowUpdater> receiver);
 
   // Sends the created child window to the browser process so that it can be
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
index b8792901aeb7d1f8ee459bab05c3cc592f38aaa1..04a8c6f55e2308e0ee545072f2fe5c53641a8607 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -31,6 +31,7 @@ struct RootCompositorFrameSinkParams {
   bool send_swap_size_notifications = false;
   // Disables begin frame rate limiting for the display compositor.
   bool disable_frame_rate_limit = false;
+  bool offscreen = false;
 
   [EnableIf=is_android]
   float refresh_rate;
diff --git a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom
index 2f462f0deb5fc8a637457243fb5d5849fc214d14..695869b83cefaa24af93a2e11b39de05456071f3 100644
--- a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom
+++ b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom
@@ -26,5 +26,5 @@ interface LayeredWindowUpdater {
   // Draws to the HWND by copying pixels from shared memory. Callback must be
   // called after draw operation is complete to signal shared memory can be
   // modified.
-  Draw() => ();
+  Draw(gfx.mojom.Rect damage_rect) => ();
 };
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 5d3dbdb5016a266aa59df2a2954608ed377bb0a8..579b7f45fabdbf326995e3a20b5e509c859f178f 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -89,6 +89,7 @@ namespace mojom {
 class DisplayPrivate;
 class ExternalBeginFrameController;
 }  // namespace mojom
+class HostDisplayClient;
 class HostFrameSinkManager;
 class LocalSurfaceId;
 class RasterContextProvider;
@@ -140,6 +141,16 @@ class COMPOSITOR_EXPORT ContextFactory {
   virtual viz::HostFrameSinkManager* GetHostFrameSinkManager() = 0;
 };
 
+class COMPOSITOR_EXPORT CompositorDelegate {
+ public:
+  virtual bool IsOffscreen() const = 0;
+  virtual std::unique_ptr<viz::HostDisplayClient> CreateHostDisplayClient(
+      ui::Compositor* compositor) = 0;
+
+ protected:
+  virtual ~CompositorDelegate() {}
+};
+
 // Compositor object to take care of GPU painting.
 // A Browser compositor object is responsible for generating the final
 // displayable form of pixels comprising a single widget's contents. It draws an
@@ -183,6 +194,9 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,
   // Schedules a redraw of the layer tree associated with this compositor.
   void ScheduleDraw();
 
+  CompositorDelegate* delegate() const { return delegate_; }
+  void SetDelegate(CompositorDelegate* delegate) { delegate_ = delegate; }
+
   // Sets the root of the layer tree drawn by this Compositor. The root layer
   // must have no parent. The compositor's root layer is reset if the root layer
   // is destroyed. NULL can be passed to reset the root layer, in which case the
@@ -533,6 +547,8 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,
 
   std::unique_ptr<PendingBeginFrameArgs> pending_begin_frame_args_;
 
+  raw_ptr<CompositorDelegate> delegate_ = nullptr;
+
   // The root of the Layer tree drawn by this compositor.
   raw_ptr<Layer> root_layer_ = nullptr;
 
diff --git a/ui/gfx/ca_layer_params.h b/ui/gfx/ca_layer_params.h
index 86d5be8b3778ced67e65d3f1e22b6aef467c7042..a2044ee0440339247cc91e06a7a0c323cbf332fc 100644
--- a/ui/gfx/ca_layer_params.h
+++ b/ui/gfx/ca_layer_params.h
@@ -6,6 +6,7 @@
 #define UI_GFX_CA_LAYER_PARAMS_H_
 
 #include "build/build_config.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/gfx_export.h"
 
@@ -51,6 +52,8 @@ struct GFX_EXPORT CALayerParams {
   gfx::ScopedRefCountedIOSurfaceMachPort io_surface_mach_port;
 #endif
 
+  gfx::Rect damage;
+
   // The geometry of the frame.
   gfx::Size pixel_size;
   float scale_factor = 1.f;
diff --git a/ui/gfx/mojom/ca_layer_params.mojom b/ui/gfx/mojom/ca_layer_params.mojom
index 1944ef571b1f12fbee8dff553ad4de913c794efb..529296cbf858d2aaeb7b5fbe1ae8f36e4ec1d485 100644
--- a/ui/gfx/mojom/ca_layer_params.mojom
+++ b/ui/gfx/mojom/ca_layer_params.mojom
@@ -18,5 +18,6 @@ struct CALayerParams {
   bool is_empty;
   CALayerContent content;
   gfx.mojom.Size pixel_size;
+  gfx.mojom.Rect damage;
   float scale_factor;
 };
diff --git a/ui/gfx/mojom/ca_layer_params_mojom_traits.cc b/ui/gfx/mojom/ca_layer_params_mojom_traits.cc
index e1c6ed1b3a456b164945ee7eef34f9d4f0b80e07..87bff4350cdfcca97de6f66946b9cb6155e36634 100644
--- a/ui/gfx/mojom/ca_layer_params_mojom_traits.cc
+++ b/ui/gfx/mojom/ca_layer_params_mojom_traits.cc
@@ -52,6 +52,9 @@ bool StructTraits<gfx::mojom::CALayerParamsDataView, gfx::CALayerParams>::Read(
   if (!data.ReadPixelSize(&out->pixel_size))
     return false;
 
+  if (!data.ReadDamage(&out->damage))
+    return false;
+
   out->scale_factor = data.scale_factor();
   return true;
 }
diff --git a/ui/gfx/mojom/ca_layer_params_mojom_traits.h b/ui/gfx/mojom/ca_layer_params_mojom_traits.h
index b6d3f2fea1d663ee1eba82a8008afc830897534c..e06f7d3184d66d9585af39c896036c1792693ac5 100644
--- a/ui/gfx/mojom/ca_layer_params_mojom_traits.h
+++ b/ui/gfx/mojom/ca_layer_params_mojom_traits.h
@@ -20,6 +20,10 @@ struct StructTraits<gfx::mojom::CALayerParamsDataView, gfx::CALayerParams> {
     return ca_layer_params.pixel_size;
   }
 
+  static gfx::Rect damage(const gfx::CALayerParams& ca_layer_params) {
+    return ca_layer_params.damage;
+  }
+
   static float scale_factor(const gfx::CALayerParams& ca_layer_params) {
     return ca_layer_params.scale_factor;
   }
