Add background blur support inside 3D rendering contexts.#1432
Conversation
…zoom in capture viewMatrix.
shlzxjp
left a comment
There was a problem hiding this comment.
代码评审反馈(P0 + P1):以下问题建议在合入前处理。每条已对照 PR head 源码核对。
- P0:基线人工核对、
primeCompositorFromOuterCanvas静默降级、BackgroundConsumer::readCursors越界静默 skip - P1:
Layer.h新增 friend 暴露面、isCapturer()+static_cast反向类型识别、Layer3DContext::_drawFunc重复 addLayer 静默覆盖、关键 unit test 缺失
详情见各行级评论。
| "ImageWithMipmap": "1ea63c49", | ||
| "ImageWithShadow": "c37a86c8", | ||
| "Layer3DTree": "9e2c8c8ee", | ||
| "Layer3DTree": "e581709f9", |
There was a problem hiding this comment.
[P0 / Major] 老 baseline key 被 bump 需要人工核对
本次基线变更不仅有 6 个新 BackgroundBlur3DLayer_* key,还把 4 个与 BackgroundBlur 无直接关联的老 key 一并 bump:
Layer3DTree(line 169)Matrix_3D_2D_3D_Preserve3D(line 302)PartialDrawLayer、PartialDrawLayer_shapeLayer(line 306-307)
alpha 策略重构(rasterAlpha/compositeAlpha)+ BSP raster/composite 分离 + contentScale 矩阵改动很可能导致 2D/3D 的像素差异,可能是预期的精度提升,也可能是无意回归。
建议:
- PR 描述里逐 key 解释像素差异原因;
- reviewer 人工对比
_base.webp旧/新基线,确认无可见质量退化; - 截图差异不可见时,在 PR 描述附「肉眼无差/可接受」结论。
| if (primeImage != nullptr) { | ||
| _compositor->primeWithImage(primeImage); | ||
| } | ||
| } |
There was a problem hiding this comment.
[P0 / Major] primeCompositorFromOuterCanvas 静默降级丢失 backdrop
本函数有 5 处早返回路径:
- L241
outerSurface == nullptr(picture canvas / 无 surface 的 canvas) - L245
outerSnapshot == nullptr - L250
targetWidth/Height <= 0 - L260 矩阵不可逆
- L266
picture == nullptr
全部静默 return;,但 finishAndDrawTo L119-128 的 compositorSource 仍会被构造、leafCapturer 仍写 entry,consume 端读出未 prime 过的 compositor 截图——blur 看到的是仅含 BSP 累积内容的「半 backdrop」,体感为偶发深色边/缺失。
这与项目规范 .codebuddy/rules/Code.md 「执行已确认的方案时…禁止静默降级为简化方案」直接冲突。
建议:
- 让函数返回
bool;上层据此跳过compositorSource的构造,让 blur 走显式 fallback 而非读「半 backdrop」; - 失败原因加
LOGW一次(picture canvas 路径属预期,可走静默 + 文档化); - picture canvas + 3D + BackgroundBlur 在 PR 描述 / 类头注释中显式声明为「已知不支持场景」。
| auto& cursor = readCursors[key]; | ||
| if (cursor >= it->second.size()) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
[P0 / Major] cursor 越界静默 skip 隐藏 capture/consume 不一致
auto& cursor = readCursors[key];
if (cursor >= it->second.size()) {
return;
}配合 BackgroundCapturer::drawBackgroundStyle 改为 push_back(L235),capture/consume 必须严格 N:N。一旦发散即静默丢 entry,肉眼难察觉。
典型发散场景:Render3DContext::rasterLayer(Render3DContext.cpp:202-228)中 Surface::Make / createFromSurface 失败时,不写 snapshot entry,但 consume 端仍按 fragment 数循环;从失败片段起所有后续片段都从「错位」cursor 读 entry,blur 偶发缺失或张冠李戴。
建议(双管齐下):
- 此处加
DEBUG_ASSERT(cursor < it->second.size() && "capture/consume push-pop count mismatch");让发散立即暴露; Render3DContext::rasterLayer失败路径仍push_back一个image=nullptr占位 entry(消费端识别为「该片段无 backdrop」而非偏移所有后续 cursor)。
| friend class LayerProperty; | ||
| friend class LayerSerialization; | ||
| friend class OffscreenRenderer; | ||
| friend class Layer3DContext; |
There was a problem hiding this comment.
[P1 / Minor] 新增 friend 在公共头放大暴露面
Layer3DContext::collectNodes(src/layers/compositing3d/Layer3DContext.cpp:55-82)通过此 friend 直接访问:
_children、_alpha、maskOwner(L66)canPreserve3D()、hasBackgroundStyle()、computeContentBounds()、getMatrixWithScrollRect()(私有方法)
这是本 PR 唯一在公共头里放大暴露面的改动;3D 上下文反向窥视 Layer 内部,让未来 Layer 内部演进必须连带改 Layer3DContext。
建议:在 Layer 内新增私有 helper(如 forEach3DDescendant(visitor)),让 Layer3DContext 通过窄接口拿数据,去掉 friend;ROI 较高,几乎不增加代码量。
| bool capturePass = args.backgroundHandler != nullptr && args.backgroundHandler->isCapturer(); | ||
| if (_subtreeNeedsBackdrop && capturePass) { | ||
| auto* outerCapturer = static_cast<BackgroundCapturer*>(args.backgroundHandler); | ||
| outerSnapshots = outerCapturer->snapshotMap(); |
There was a problem hiding this comment.
[P1 / Minor] static_cast<BackgroundCapturer*> + isCapturer() 是手工反向类型识别
bool capturePass = args.backgroundHandler != nullptr && args.backgroundHandler->isCapturer();
if (_subtreeNeedsBackdrop && capturePass) {
auto* outerCapturer = static_cast<BackgroundCapturer*>(args.backgroundHandler);
outerSnapshots = outerCapturer->snapshotMap();项目规范禁用 dynamic_cast,当前用 isCapturer() 虚函数 + static_cast 是其等价替代品。能工作的前提是「仅 BackgroundCapturer 返回 true」——一旦未来新增 capture 系子类(如 SubBackgroundCapturer)违反此约定,static_cast 即 UB。同时 isCapturer() 与 snapshotMap() 仅供 3D 模块使用却作为 public 虚函数暴露在基类。
建议:基类增 virtual BackgroundCapturer* asCapturer() { return nullptr; },BackgroundCapturer override 返回 this;调用点改为:
if (auto* outerCapturer = args.backgroundHandler ? args.backgroundHandler->asCapturer() : nullptr) { ... }这能同时去掉 isCapturer() 与 snapshotMap() 两个 public 接口,让类型契约由编译器守护。
| return _transformStack.empty() ? Matrix3D::I() : _transformStack.top().transform; | ||
| void Layer3DContext::addLayer(Layer* layer, const Matrix3D& transform, float alpha, | ||
| LayerDrawFunc drawFunc) { | ||
| _drawFunc = drawFunc; |
There was a problem hiding this comment.
[P1 / Minor] _drawFunc 共享状态被二次 addLayer 静默覆盖
void Layer3DContext::addLayer(Layer* layer, const Matrix3D& transform, float alpha,
LayerDrawFunc drawFunc) {
_drawFunc = drawFunc; // 第二次调用会无声覆盖第一次的 drawFunc
collectNodes(layer, transform, alpha, /*depth=*/0);
}当前调用约定是「每个 Layer3DContext 实例只 addLayer 一次再 finish」,但接口未阻止重复调用,第二次调用静默覆盖第一次。未来若新增 「同一 3D 上下文中混合 drawLayer / drawContour 节点」 这类扩展,bug 难以察觉。
建议(任选其一):
- 加
DEBUG_ASSERT(_drawFunc == nullptr || _drawFunc == drawFunc);(最小修改); - 把
drawFunc下沉为PendingNode字段,实现「不同节点不同 drawFunc」(更彻底)。
|
|
||
| displayList->render(surface.get()); | ||
| EXPECT_TRUE(Baseline::Compare(surface, "BackgroundBlurTest/BackgroundBlur3DLayer_Nested3D")); | ||
| } |
There was a problem hiding this comment.
[P1 / Minor] 关键 unit test 覆盖缺失
本 PR 同时删除了 LayerTest.Layer3DContextAPI(原覆盖 stack 模型 begin/end recording 嵌套)但未补回新两阶段 API 的等价测试。综合复核后建议补 4 类:
Layer3DContext两阶段 API unit test:addLayer/finishAndDrawTo/emitNode的调用顺序、空_pendingNodes早返回、嵌套 3D 上下文render3DContext设置/恢复等。BackgroundConsumer多游标行为:直接构造BackgroundSnapshotMap+ 同 key 多次push_back,串行喂两个BackgroundConsumer,验证游标互不干扰。这是本 PR 新增的核心机制,目前只有端到端截图间接覆盖。- GroupOpacity on/off 双 alpha 策略:3D 子树内显式切换
setAllowsGroupOpacity(true/false)验证rasterAlpha(baked 进 image)vscompositeAlpha(顶点色)两条路径分别生效。 - picture canvas + 3D + BackgroundBlur fallback:当前所有 case 都走 GPU 路径,需补 picture canvas 上 3D + BackgroundBlur 不崩溃且行为可预期(与上文 P0「
primeCompositorFromOuterCanvas静默降级」配套)。
建议加在 BackgroundBlur3DLayer 之后,或独立放回 LayerTest.cpp 的 Layer3DContextAPI 位置。
| layerHasBackgroundStyle.emplace(node.layer, node.hasBackgroundStyle); | ||
| } | ||
| DrawArgs leafArgs = args; | ||
| leafArgs.opaqueContext = nullptr; |
There was a problem hiding this comment.
这一行 leafArgs.opaqueContext = nullptr; 是死代码:Render3DContext 只在 Create3DContext() 中 opaqueMode == false(即 args.opaqueContext == nullptr)时才会被构造,因此 args.opaqueContext 进入这里时一定是 null,重置没有意义。建议删除这一行。
| if (_pendingNodes.empty()) { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
这一行 leafArgs.opaqueContext = nullptr; 是死代码:循环里每个节点都会用 nodeArgs = leafArgs 拷贝一次后立即 nodeArgs.opaqueContext = &opaqueContext; 覆盖,初值永远不会被任何 drawWithFunc 读到。建议删除这一行。
| BackgroundSnapshotKeyHash> { | ||
| struct BackgroundSnapshotMap { | ||
| std::unordered_map<BackgroundSnapshotKey, std::vector<BackgroundSnapshotEntry>, | ||
| BackgroundSnapshotKeyHash> |
There was a problem hiding this comment.
建议在这里的注释中再点一句:消费者(BackgroundConsumer)现在会通过自己的 readCursors 状态读取此 map,外部只读 snapshots 字段。这样读者一眼就能理解 DisplayList::drawTileTask / drawRootLayer 的签名为何从 const BackgroundSnapshotMap* 改成非 const —— 不是某个调用点真的要写入 snapshots,而是消费者一侧的 cursor 状态绑定到了 consumer 自己。
|
@shlzxjp Re: [P1 / Minor] 新增 friend 在公共头放大暴露面 — Layer3DContext 属于 Layer 合成子系统的一部分(与已有的 BackgroundCapturer/BackgroundConsumer friend 同层),collectNodes 访问的 _children / _alpha / maskOwner 等都是 Layer 内部合成路径的数据,friend 关系合理,不需要额外窄接口。 |
| // Functor adapter so BspTree::traverseBackToFront — which expects a callable taking | ||
| // const DrawPolygon3D* — can collect fragments into a vector without a lambda. | ||
| struct CollectFragmentsAction { | ||
| std::vector<DrawPolygon3D*>* output = nullptr; |
There was a problem hiding this comment.
这个地方应该统一起来,使用std::vector<const DrawPolygon3D*>* output = nullptr;
operator的参数是const,预期不可修改,push进output时强制解除了const限定,语义上是冲突的,统一下
在 preserve3D 子树内补齐 BackgroundBlur 等背景样式的支持,使 3D 子树中的图层可以正常使用 BackgroundBlurStyle。
主要改动:
3D 合成两阶段流水线:将 Layer3DContext 改为 addLayer + finishAndDrawTo 两阶段——先收集图层、再统一完成并绘制到目标,便于分别处理几何收集和栅格合成。
复用标准 Background 流水线:通过文件内私有的 Compositor3DBackgroundSource 让 3D 片段的栅格化复用 BackgroundCapturer 流水线,片段内分发以及嵌套 offscreen 都走 createSubHandler 同一条路径,去掉 3D 专用的 background 处理代码。
alpha 策略:DrawPolygon3D 暴露 rasterAlpha / compositeAlpha,由合成器与栅格 pass 各自施加一次 per-layer alpha,由 allowsGroupOpacity 控制启用。
snapshot map 多消费者:将读游标从 BackgroundSnapshotMap 移到 BackgroundConsumer 上,让同一份 snapshot map 可以被多个 consumer 共享(tiled 渲染、部分缓存 pass);同时在 3D 子树捕获阶段保留外层 canvas 已有的 zoom / translate。
修复 Layer::draw 路径下背景捕获错位:drawRect 改用 device 像素空间(outerCanvas.mapRect(rectForDraw)),避免 globalMatrix 含 perspective 时 bgSurface 大小与位置错位、背景丢失。
内部架构清理:将 3D collectNodes 共享遍历与 drawFunc 上移到 Layer3DContext 基类,subclass 通过 emitNode 钩子注册节点;修复 Render3DContext::rasterLayer 在跨片段复用 leafArgs 时残留的悬空 BackgroundHandler 指针。
测试:BackgroundBlurTest.BackgroundBlur3DLayer 覆盖 Preserve3D / Flat / Preserve3DFallback / DirectDraw / NestedOffscreen / Nested3D 等场景,截图基线已更新。
Based on #1384 by @StarryThrone.
Co-authored-by: StarryThrone 18690813+StarryThrone@users.noreply.github.com