From 40129147c64fbf6f7f021ddbeed7f10a3aa5f91b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 5 Aug 2025 20:40:33 -0700 Subject: [PATCH] Respect paths' content masks when copying them from MSAA texture to drawable (#35688) Fixes a regression introduced in https://github.com/zed-industries/zed/pull/34992 ### Background Paths are rendered first to an intermediate MSAA texture, and then copied to the final drawable. Because paths can have transparency, it's important that pixels are not copied repeatedly if paths have overlapping bounding boxes. When N paths have the same draw order, we infer that they must have disjoint bounding boxes, so that we can copy them each individually (as opposed to copying a single rect that contains them all). Previously, the bounding box that we were using to copy paths was not accounting for the path's content mask (but it is accounted for in the bounds tree that determines their draw order). This cause bugs like this, where certain path pixels spuriously had their opacity doubled: https://github.com/user-attachments/assets/d792e60c-790b-49ad-b435-6695daba430f This PR fixes that bug. * [x] mac * [x] linux * [x] windows Release Notes: - Fixed a bug where a selection's opacity was computed incorrectly when it overlapped with another editor's selections in a certain way. --- .../gpui/src/platform/blade/blade_renderer.rs | 8 ++++---- crates/gpui/src/platform/mac/metal_renderer.rs | 6 +++--- .../src/platform/windows/directx_renderer.rs | 8 ++++---- crates/gpui/src/scene.rs | 17 ++++++++++++++++- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 2e18d2be22..46d3c16c72 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -606,7 +606,7 @@ impl BladeRenderer { xy_position: v.xy_position, st_position: v.st_position, color: path.color, - bounds: path.bounds.intersect(&path.content_mask.bounds), + bounds: path.clipped_bounds(), })); } let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) }; @@ -735,13 +735,13 @@ impl BladeRenderer { paths .iter() .map(|path| PathSprite { - bounds: path.bounds, + bounds: path.clipped_bounds(), }) .collect() } else { - let mut bounds = first_path.bounds; + let mut bounds = first_path.clipped_bounds(); for path in paths.iter().skip(1) { - bounds = bounds.union(&path.bounds); + bounds = bounds.union(&path.clipped_bounds()); } vec![PathSprite { bounds }] }; diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index fb5cb852d6..629654014d 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -791,13 +791,13 @@ impl MetalRenderer { sprites = paths .iter() .map(|path| PathSprite { - bounds: path.bounds, + bounds: path.clipped_bounds(), }) .collect(); } else { - let mut bounds = first_path.bounds; + let mut bounds = first_path.clipped_bounds(); for path in paths.iter().skip(1) { - bounds = bounds.union(&path.bounds); + bounds = bounds.union(&path.clipped_bounds()); } sprites = vec![PathSprite { bounds }]; } diff --git a/crates/gpui/src/platform/windows/directx_renderer.rs b/crates/gpui/src/platform/windows/directx_renderer.rs index 72cc12a5b4..ac285b79ac 100644 --- a/crates/gpui/src/platform/windows/directx_renderer.rs +++ b/crates/gpui/src/platform/windows/directx_renderer.rs @@ -435,7 +435,7 @@ impl DirectXRenderer { xy_position: v.xy_position, st_position: v.st_position, color: path.color, - bounds: path.bounds.intersect(&path.content_mask.bounds), + bounds: path.clipped_bounds(), })); } @@ -487,13 +487,13 @@ impl DirectXRenderer { paths .iter() .map(|path| PathSprite { - bounds: path.bounds, + bounds: path.clipped_bounds(), }) .collect::>() } else { - let mut bounds = first_path.bounds; + let mut bounds = first_path.clipped_bounds(); for path in paths.iter().skip(1) { - bounds = bounds.union(&path.bounds); + bounds = bounds.union(&path.clipped_bounds()); } vec![PathSprite { bounds }] }; diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index ec8d720cdf..c527dfe750 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -8,7 +8,12 @@ use crate::{ AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point, }; -use std::{fmt::Debug, iter::Peekable, ops::Range, slice}; +use std::{ + fmt::Debug, + iter::Peekable, + ops::{Add, Range, Sub}, + slice, +}; #[allow(non_camel_case_types, unused)] pub(crate) type PathVertex_ScaledPixels = PathVertex; @@ -793,6 +798,16 @@ impl Path { } } +impl Path +where + T: Clone + Debug + Default + PartialEq + PartialOrd + Add + Sub, +{ + #[allow(unused)] + pub(crate) fn clipped_bounds(&self) -> Bounds { + self.bounds.intersect(&self.content_mask.bounds) + } +} + impl From> for Primitive { fn from(path: Path) -> Self { Primitive::Path(path)