Skip to content

Add analytic clip fragment processors for affine-mapped rects and rounded rects to avoid mask textures. - #1495

Merged
Hparty merged 19 commits into
mainfrom
feature/jasonrjchen_rrect_clip_analytic
Jul 16, 2026
Merged

Add analytic clip fragment processors for affine-mapped rects and rounded rects to avoid mask textures.#1495
Hparty merged 19 commits into
mainfrom
feature/jasonrjchen_rrect_clip_analytic

Conversation

@StarryThrone

Copy link
Copy Markdown
Collaborator

为仿射变换下的矩形和圆角矩形裁剪引入解析式 coverage FragmentProcessor(RectEffect / RRectEffect),在 shader 内直接计算覆盖率,避免额外分配 mask 纹理和渲染 pass。

主要改动:

  • 新增 RectEffect / RRectEffect 及对应 GLSL 实现,取代旧的 GLSLAARectEffect;RRect 的 Simple / Complex 类型统一走同一套 arc-box 分派 + 椭圆 SDF 逻辑,仅按是否恒等矩阵区分程序。
  • OpsCompositor::applyClip 优先走解析式 FP 路径,限制链长为 4,超出部分回退到 mask 纹理路径;透视 / path / empty 元素回退到 mask。
  • ClipStack、RRectUtils、GeometryShape 相关支持逻辑,包括 device-space path、保守求交等。
  • 补充 ClipTest / RRectUtilsTest 测试用例,更新截图 baseline。

…the obsolete GLSLAARectEffect and porting its highp precision fix into the new GLSLRectEffect and GLSLRRectEffect shaders.
- Fix DEBUG_ASSERT_RESULT dropping its expression side effect under NO_LOG.
- Drop the unreachable perspective note from Rect and RRect effect key comments.
- Fix a stale function name and remove verifying phrasing in ClipTest comments.
- Rename kScale to diagInsetScale to drop the disallowed k prefix.
- Document the invariant guarding the RRect axis-aligned transform assert in simplify, with a concrete degenerate transform example.
- Remove orphaned CanvasTest clip baseline keys after the test migration to ClipTest.
Comment thread src/core/ClipStack.cpp Outdated
Path curBoundsPath = {};
curBoundsPath.addRect(cur.bounds);
const ClipElement curElement(curBoundsPath, false);
const ClipElement curElement(GeometryShape(curBoundsPath), Matrix::I(), false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为把 cur.bounds 包成 ClipElement,先构造了一个 Path 再传入 GeometryShape(Path 类型),而 ClipElement 构造函数里的 simplify() 会立刻把它降级回 Rect 类型并销毁该 Path。这涉及一次 PathRef(shared_ptr)的堆分配与释放,纯属浪费。cur.bounds 本身就是 Rect,可直接用 Rect 构造,产生完全等价的元素(simplify 后同为 Rect 类型、相同 rect 及 outer/inner bounds),无需 Path:

const ClipElement curElement(GeometryShape(cur.bounds), Matrix::I(), false);

该路径在每次非首个裁剪元素加入时都会执行,属热点,建议改用 Rect 构造避免分配。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已经修复。

Comment thread src/core/ClipStack.cpp
if (IsClipPixelAligned(_shape.rect())) {
// Case 1a: this is pixel-aligned so AA has no visual effect, adopt other's AA flag.
_antiAlias = other._antiAlias;
} else if (!IsClipPixelAligned(other._shape.rect())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是不是可以判断下是否100%包含,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在 UpdateElements 中走到 tryCombine 时,上游已保证不存在两个元素互相包含的场景,因此这里无需再做 100% 包含判断。

Comment thread src/core/ClipStack.cpp
if (transformed.has_value()) {
_matrix.setIdentity();
if (transformed->type() == RRect::Type::Rect) {
_shape.setRect(transformed->rect());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会生成Rect吗,进来的时候都是RRect了

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

会走到 setRect。进来虽是 RRect,但经 TryAxisAlignedTransform 变换后半径可能退化归零:RRect::setRectRadii 里的 ScaleRadii/FlushToZero 会把被大值吸收的极小半径归零,四角全零后 ComputeType 将类型归一化为 Rect。例如 rect={0,0,10,10}、四角半径 {1e-6, 1e-6} 的 RRect 经 scale(1e-3, 1e-3) 变换后,半径约 1e-9,与边长 0.01 相加时被 float 吸收归零,transformed->type() 即为 RRect::Type::Rect,走此分支。

Comment thread src/core/GeometryShape.h
namespace tgfx {

/**
* GeometryShape is a tagged union over geometric primitives: an empty shape, an axis-aligned rect,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an axis-aligned rect 这个不只是像素对齐的rect吧

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里 "axis-aligned rect" 指轴对齐矩形,即矩形四条边与坐标系 x/y 轴平行,与像素对齐无关。

Comment thread src/core/PictureRecords.h
for (size_t i = clip.oldestValidIndex(); i < elements.size(); ++i) {
const auto& element = elements[i];
if (element.isValid() && element.path().isInverseFillType()) {
if (element.isValid() && element.shape().isPath() &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个和path的关系是?其他场景不是也是hasInverseClip吗

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对一个 Picture 里的裁剪记录,它总是早于实际绘制指令被处理,需要更新整个 Picture 的裁剪状态是否包含未限定的裁剪区(即实际内容是否需绘制到一个没有限制的区域内)。裁剪元素类型中只有 path 可能被设为反转状态来表示未限定区域,其他图形都是有限区域,所以只判断 path。

static constexpr float BOUNDS_TOLERANCE = 1e-3f;
static constexpr float CLIP_BOUNDS_TOLERANCE = 1e-3f;

inline bool IsPixelAligned(float value) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个行为只和clip相关了吗

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前仅用于裁剪区。该安全阈值是按裁剪区用覆盖率模拟元素通过情况计算得出的,其他场景未必安全,因此特意加 CLIP 前缀提醒开发者注意使用场景。

Comment thread src/gpu/OpsCompositor.cpp
return out;
}

PlacementPtr<FragmentProcessor> OpsCompositor::makeAnalyticFP(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是否判断PlacementPtr状态就能处理?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不用bool返回值

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里返回 bool 是有意的。tryApplyAnalyticFP 内部不仅创建解析 FP,还负责把它与传入的 inputFP 合并成链(FragmentProcessor::Compose)。合并后返回的指针在成功和失败时都可能非空(失败时返回原样的 inputFP 累积链),因此外部无法仅凭指针是否为空判断是否成功创建了解析 FP,需要 bool 显式区分。把合并逻辑收敛到函数内部是为了让调用方统一处理链的累积,代价就是需要额外的成功标志。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

理解在当前设计下 bool 是必需的,也认同它与 getClipMaskFP 都在内部接管 Compose、保持了对称。不过换个角度:累积链 clipFP 本就是 applyClip 循环在维护的状态,若把 Compose 从 tryApplyAnalyticFP 收回循环里,函数退化为「只为单个元素造 FP,失败返回 nullptr」的纯转换,返回值语义单一、bool 即可去除,也更好测试。代价是 getClipMaskFP 也应同步把 Compose 收回循环以保持一致,改动涉及三处。功能无 bug,仅是职责纯度上的可选优化,供参考,是否调整由你权衡。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

想了下这个重构不做了。clip 链每个节点都是 out = in * coverage(RectEffect/RRectEffect 的 shader 输出 inputColor * coverage,mask 侧是 MulInputByChildAlpha),整链就是标量 coverage 连乘。tryApplyAnalyticFP 和 makeMaskFP「基于已有 FP 追加本阶段 FP」本身是内聚的职责,不算职责不纯。把 Compose 抽回循环会让 makeMaskFP 多一个永远传 nullptr 的死参数、Compose 顺序约束从函数内散到调用点,换来的只是去掉一个 bool,且改的是 clip 合成核心路径。无 bug、无真实痛点,收益不抵成本,保持现状。

…t leave uncleared stencil regions.

glClear is affected by the scissor test, so a scissor rect left enabled by a previous render pass restricted the depth/stencil clear to that region, leaving stale stencil values that corrupted the stencil-and-cover inner-stroke rendering of adjacent shapes.
@Hparty
Hparty merged commit 2b9b52c into main Jul 16, 2026
14 checks passed
@Hparty
Hparty deleted the feature/jasonrjchen_rrect_clip_analytic branch July 16, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants