Repository: electron/electron
Pull request: fix: visual artifacts while resizing on Windows

#### Description of Change

Refs https://github.com/electron/electron/issues/36280#issuecomment-3560964534

This fully fixes the stale pixels that we saw while resizing a window on Windows. 

Manual backports of [crrev.com/c/7210913](https://crrev.com/c/7210913) and [crrev.com/c/7115438](https://crrev.com/c/7115438).

v41 has picked up the patches through https://github.com/electron/electron/pull/49145.

I backported more patches to v39 than in this PR. On v40, they have been picked up through Chromium rolls.

My work on this PR was funded by [Plasticity](https://www.plasticity.xyz/).

#### Checklist

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed visual artifacts while resizing a window on Windows.


Changed files:
filename: patches/chromium/.patches
patch: @@ -141,3 +141,5 @@ allow_electron_to_depend_on_components_os_crypt_sync.patch
 expose_referrerscriptinfo_hostdefinedoptionsindex.patch
 chore_disable_protocol_handler_dcheck.patch
 fix_check_for_file_existence_before_setting_mtime.patch
+viz_create_isbufferqueuesupportedandenabled.patch
+viz_fix_visual_artifacts_while_resizing_window_with_dcomp.patch

filename: patches/chromium/viz_create_isbufferqueuesupportedandenabled.patch
patch: @@ -0,0 +1,93 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Niklas Wenzel <dev@nikwen.de>
+Date: Tue, 2 Dec 2025 17:53:11 -0800
+Subject: viz: Create IsBufferQueueSupportedAndEnabled()
+
+Manual backport of crrev.com/c/7210913
+
+Bug: 457463689
+Change-Id: I31bbaa6b5d79697c6bb5e1fc6738f6ea5a937b4f
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7210913
+Reviewed-by: Michael Tang <tangm@microsoft.com>
+Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
+Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#1553190}
+
+diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
+index ff795eb057ac64f40aa842fec8e053d12f57f538..71287614c627d39f8d019889498205ab3eff2a69 100644
+--- a/components/viz/service/display/output_surface.cc
++++ b/components/viz/service/display/output_surface.cc
+@@ -21,6 +21,16 @@
+ 
+ namespace viz {
+ 
++namespace {
++
++#if BUILDFLAG(IS_WIN)
++// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
++// surface.
++BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
++#endif
++
++}  // namespace
++
+ OutputSurface::Capabilities::Capabilities() = default;
+ OutputSurface::Capabilities::~Capabilities() = default;
+ OutputSurface::Capabilities::Capabilities(const Capabilities& capabilities) =
+@@ -94,6 +104,12 @@ bool IsDelegatedCompositingSupportedAndEnabled(
+   // Ensure we check the feature flag iff the feature is supported.
+   return features::IsDelegatedCompositingEnabled();
+ }
++
++bool IsBufferQueueSupportedAndEnabled(
++    OutputSurface::DCSupportLevel support_level) {
++  return support_level >= OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
++         base::FeatureList::IsEnabled(kBufferQueue);
++}
+ #endif
+ 
+ }  // namespace viz
+diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
+index 25306ab6e18a266efdc329e4ddd81f5303033f4c..589f4c10dad9c807c9e3ce7baba63795b629435b 100644
+--- a/components/viz/service/display/output_surface.h
++++ b/components/viz/service/display/output_surface.h
+@@ -307,6 +307,9 @@ class VIZ_SERVICE_EXPORT OutputSurface {
+ // `features::IsDelegatedCompositingEnabled()`.
+ bool IsDelegatedCompositingSupportedAndEnabled(
+     OutputSurface::DCSupportLevel support_level);
++
++bool IsBufferQueueSupportedAndEnabled(
++    OutputSurface::DCSupportLevel support_level);
+ #endif
+ 
+ }  // namespace viz
+diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
+index f94826fe8d7c3f407695fa3161f6ae9cd949f99f..610490f50b2e355382f2610c556b8a0683023b06 100644
+--- a/components/viz/service/display/skia_renderer.cc
++++ b/components/viz/service/display/skia_renderer.cc
+@@ -120,12 +120,6 @@ namespace {
+ BASE_FEATURE(kDumpWithoutCrashingOnMissingRenderPassBacking,
+              base::FEATURE_ENABLED_BY_DEFAULT);
+ 
+-#if BUILDFLAG(IS_WIN)
+-// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
+-// surface.
+-BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
+-#endif
+-
+ // Smallest unit that impacts anti-aliasing output. We use this to determine
+ // when an exterior edge (with AA) has been clipped (no AA). The specific value
+ // was chosen to match that used by gl_renderer.
+@@ -991,10 +985,8 @@ SkiaRenderer::SkiaRenderer(const RendererSettings* settings,
+ 
+   // It's possible to use BufferQueue with DComp textures, so we can optionally
+   // enable it behind a feature flag.
+-  const bool want_buffer_queue =
+-      output_surface_->capabilities().dc_support_level >=
+-          OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
+-      base::FeatureList::IsEnabled(kBufferQueue);
++  const bool want_buffer_queue = IsBufferQueueSupportedAndEnabled(
++      output_surface_->capabilities().dc_support_level);
+ #else
+   const bool want_buffer_queue = true;
+ #endif

filename: patches/chromium/viz_fix_visual_artifacts_while_resizing_window_with_dcomp.patch
patch: @@ -0,0 +1,122 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Niklas Wenzel <dev@nikwen.de>
+Date: Tue, 2 Dec 2025 17:55:29 -0800
+Subject: viz: Fix visual artifacts while resizing window with DComp
+
+Manual backport of crrev.com/c/7115438
+
+Bug: 457463689
+Change-Id: I9c684effe15e0b112ae533faa243e5a035e9c875
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7115438
+Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
+Reviewed-by: Michael Tang <tangm@microsoft.com>
+Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
+Cr-Commit-Position: refs/heads/main@{#1553192}
+
+diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
+index 6a28b4e3d9b8d1988e96c6ae9fd931d20af5a876..19e71c4aee88824458cd1b1557e9cd770f025704 100644
+--- a/components/viz/service/display/direct_renderer.cc
++++ b/components/viz/service/display/direct_renderer.cc
+@@ -227,6 +227,31 @@ void DirectRenderer::DrawFrame(
+   current_frame()->device_viewport_size = device_viewport_size;
+   current_frame()->display_color_spaces = display_color_spaces;
+ 
++  gfx::Size surface_resource_size =
++      CalculateSizeForOutputSurface(device_viewport_size);
++
++#if BUILDFLAG(IS_WIN)
++  if (output_surface_->capabilities().clear_drawn_areas_outside_viewport &&
++      device_viewport_size != surface_resource_size) {
++    // On Windows with DirectComposition, we cannot synchronize the swap chain
++    // |Present| and the DComp |Commit| calls to take effect at the same time.
++    // (Both take effect asynchronously.) Hence, presenting a frame and changing
++    // the DComp layer clip rect can happen at different times. This can lead to
++    // ugly visual artifacts while resizing the window because it can reveal
++    // areas of the surface that are outside the viewport (crbug.com/457463689).
++    // To prevent those artifacts, we clear areas outside of the viewport with a
++    // transparent color. Transparency is expensive, so we use it only while
++    // resizing.
++    // This line gives us a transparent image format and triggers the background
++    // to be cleared in |SkiaRenderer::ClearFramebuffer|.
++    root_render_pass->has_transparent_background = true;
++    // Redraw and swap the whole surface.
++    root_render_pass->output_rect = gfx::Rect(surface_resource_size);
++    current_frame()->root_damage_rect = gfx::Rect(surface_resource_size);
++    current_frame()->device_viewport_size = surface_resource_size;
++  }
++#endif
++
+   output_surface_->SetNeedsMeasureNextDrawLatency();
+   BeginDrawingFrame();
+ 
+@@ -256,8 +281,6 @@ void DirectRenderer::DrawFrame(
+       current_frame()->display_color_spaces.GetOutputFormat(
+           current_frame()->root_render_pass->content_color_usage,
+           current_frame()->root_render_pass->has_transparent_background);
+-  gfx::Size surface_resource_size =
+-      CalculateSizeForOutputSurface(device_viewport_size);
+ #if BUILDFLAG(IS_WIN)
+   bool has_primary_plane = false;
+ #endif
+@@ -401,8 +424,10 @@ void DirectRenderer::DrawFrame(
+ 
+   // If we need to redraw the frame, the whole output should be considered
+   // damaged.
+-  if (needs_full_frame_redraw)
+-    current_frame()->root_damage_rect = gfx::Rect(device_viewport_size);
++  if (needs_full_frame_redraw) {
++    current_frame()->root_damage_rect =
++        gfx::Rect(current_frame()->device_viewport_size);
++  }
+ 
+   if (!skip_drawing_root_render_pass) {
+     DrawRenderPassAndExecuteCopyRequests(root_render_pass);
+diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
+index 589f4c10dad9c807c9e3ce7baba63795b629435b..641bbfc732c88141ddd929a4c334360462259ee4 100644
+--- a/components/viz/service/display/output_surface.h
++++ b/components/viz/service/display/output_surface.h
+@@ -106,6 +106,11 @@ class VIZ_SERVICE_EXPORT OutputSurface {
+ #if BUILDFLAG(IS_WIN)
+     // Whether this OutputSurface supports direct composition layers.
+     DCSupportLevel dc_support_level = DCSupportLevel::kNone;
++    // Whether to 1) clear all drawn areas outside the viewport with a
++    // transparent background color when drawing a frame and 2) swap them. This
++    // is necessary if the surface clip rect can get out of sync with the
++    // viewport size (e.g., due to a race condition).
++    bool clear_drawn_areas_outside_viewport = false;
+ #endif
+     // Whether this OutputSurface should skip DrawAndSwap(). This is true for
+     // the unified display on Chrome OS. All drawing is handled by the physical
+diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+index 7ac9ea1cdc4416a7af8dc2a75404cbc15be6cfad..8e027d1382c5d639c1e114b8e25b6cea3af3445d 100644
+--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
++++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+@@ -39,10 +39,8 @@
+ namespace viz {
+ 
+ namespace {
+-// With DirectComposition, resize surface based on root render pass size to
+-// avoid gutter which shows stale pixels.
+-BASE_FEATURE(kDirectCompositionResizeBasedOnRootSurface,
+-             base::FEATURE_ENABLED_BY_DEFAULT);
++// Apply fixes for crbug.com/457463689.
++BASE_FEATURE(kDirectCompositionResizeFixes, base::FEATURE_ENABLED_BY_DEFAULT);
+ 
+ base::TimeTicks g_last_reshape_failure = base::TimeTicks();
+ 
+@@ -161,7 +159,14 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
+   capabilities_.supports_viewporter = presenter_->SupportsViewporter();
+   capabilities_.supports_non_backed_solid_color_overlays = true;
+   capabilities_.resize_based_on_root_surface =
+-      base::FeatureList::IsEnabled(kDirectCompositionResizeBasedOnRootSurface);
++      base::FeatureList::IsEnabled(kDirectCompositionResizeFixes);
++  // With delegated compositing or a buffer queue, |Present| and |Commit| are
++  // synchronized and the clear is not needed.
++  capabilities_.clear_drawn_areas_outside_viewport =
++      base::FeatureList::IsEnabled(kDirectCompositionResizeFixes) &&
++      !IsDelegatedCompositingSupportedAndEnabled(
++          capabilities_.dc_support_level) &&
++      !IsBufferQueueSupportedAndEnabled(capabilities_.dc_support_level);
+ 
+   DCHECK(context_state_);
+   DCHECK(context_state_->gr_context() ||