Add DepthStencilTextureView to share one stencil attachment across same-spec render targets. - #1514
Add DepthStencilTextureView to share one stencil attachment across same-spec render targets.#1514StarryThrone wants to merge 12 commits into
Conversation
…me-spec render targets. 将 RenderTargetProxy 私有创建的 stencil 改为经 ResourceCache 按 spec(width/height/sampleCount)派生 unique key 命中共享的 DepthStencilTextureView,OpsRenderTask 经 getStencil 取用共享 attachment;补充 proxy 内复用、跨 proxy 共享、spec 隔离及多 render pass 复用清理的测试。
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
Hparty
left a comment
There was a problem hiding this comment.
Reviewed by CodeBuddy: two follow-ups, no blocking issues.
| ASSERT_TRUE(proxy != nullptr); | ||
| auto stencil = proxy->getStencil(1); | ||
| ASSERT_TRUE(stencil != nullptr); | ||
| EXPECT_EQ(stencil.get(), first.get()); |
There was a problem hiding this comment.
测试盲区:Case 2 断言 stencil.get() == first.get() 时,first 仍持有强引用,只覆盖了"资源被引用期间的共享"路径。全局缓存的核心场景是:所有代理释放后附件进入 purgeableResources,新代理再通过 refResource 重新激活同一实例。建议在 Case 2 前保存裸指针并释放 first(如 auto* shared = first.get(); first.reset();),再断言 stencil.get() == shared,以覆盖 purgeable -> 重新激活的完整生命周期(注意 Case 3 的 EXPECT_NE 需改用该裸指针比较)。另 Dispatch_StencilReuseAcrossPasses 全程复用同一 surface,也未触发跨代理共享。
| if (stencilAttachment != nullptr) { | ||
| // The cached entry must match the requested sample count; otherwise the caller is mixing | ||
| // MSAA configurations on the same proxy, which would silently violate the "all attachments | ||
| // share one sampleCount" rule that every backend enforces. |
There was a problem hiding this comment.
sampleCount 不匹配仅由 DEBUG_ASSERT 防护,release 构建下同一代理混用 getStencil(1)/getStencil(4) 会静默返回错误 sampleCount 的附件。契约已在头文件注释中声明且当前调用方不可触发,仅建议加固:release 下返回 nullptr(OpsRenderTask.cpp 已能处理 null 并跳过 stencil op),使契约在 release 下也安全。
|
测试盲区 G1(混合 pass 回归): // Regression for the mixed-pass scenario: a stencil-cover path op and a non-stencil draw op
// sharing one render pass. Both ops are queued in a single flush, so OpsRenderTask attaches a
// depth-stencil texture to the render pass for every op in the task. The plain rect's
// pipeline therefore runs against a pass that carries a depth-stencil attachment, which is
// exactly the case StandardDrawOp::bindStandardPipeline handles by inheriting
// RenderPass::depthStencilFormat() instead of leaving the depth-stencil format unknown, and
// the stencil/cover descriptors follow the same format instead of hardcoding
// DEPTH24_STENCIL8. On Metal a pipeline whose declared depth-stencil format does not match the
// attachment is rejected at creation time, so this combination used to crash before the fix;
// the assertions below pin down that both the stencil-filled pentagon and the plain rect
// still rasterize correctly when they share the pass.
TGFX_TEST(StencilCoverPathTest, Dispatch_StencilCoverAndRectShareRenderPass) {
ContextScope scope;
auto context = scope.getContext();
ASSERT_TRUE(context != nullptr);
ScopedStencilCoverCaps capsGuard(context, true);
constexpr int kSize = 64;
auto surface = Surface::Make(context, kSize, kSize);
ASSERT_TRUE(surface != nullptr);
auto canvas = surface->getCanvas();
canvas->clear(Color{0.f, 0.f, 0.f, 1.f});
Paint paint;
paint.setAntiAlias(false);
paint.setColor(Color{1.f, 0.f, 0.f, 1.f});
// Op A: a stencil-cover pentagon in the left half. It is a non-rect shape without AA, so it
// is dispatched through the stencil-and-cover pipeline (pendingStencilCoverShapes).
Path path = BuildCellPentagon(16, 32);
path.setFillType(PathFillType::Winding);
canvas->drawPath(path, paint);
// Op B: a plain filled rect in the right half, in the same flush so both ops end up in one
// OpsRenderTask and therefore share one render pass. The rect never touches the stencil
// buffer, but its pipeline must still declare the pass's depth-stencil format on backends
// that reject a format-less pipeline against a depth-stencil-bearing pass.
canvas->drawRect(Rect::MakeXYWH(40, 20, 20, 24), paint);
context->flushAndSubmit();
// Inside pentagon A: stencil+cover fill, must be red.
EXPECT_EQ(ReadRedChannel(surface.get(), 16, 32), 0xFF)
<< "Pentagon interior (stencil op) should be red in the mixed pass";
// Outside pentagon A (top-left corner): stencil-culled, must stay black.
EXPECT_EQ(ReadRedChannel(surface.get(), 4, 4), 0x00)
<< "Outside pentagon A should stay black in the mixed pass";
// Inside rect B: plain fill sharing the stencil-bearing pass, must be red.
EXPECT_EQ(ReadRedChannel(surface.get(), 50, 32), 0xFF)
<< "Rect interior (non-stencil op) should be red in the mixed pass";
// Outside everything (bottom-right corner): must stay black.
EXPECT_EQ(ReadRedChannel(surface.get(), 62, 62), 0x00)
<< "Outside both shapes should stay black in the mixed pass";
}验证情况:该用例在 OpenGL 与 Metal 均通过;Metal 下 |
背景:此前 stencil 由每个 RenderTargetProxy 私有创建并持有,同规格的多个 RenderTarget 各自占用一块 depth/stencil 显存,无法复用。
改动:新增 DepthStencilTextureView 可缓存资源,按规格(width/height/sampleCount)派生 UniqueKey 纳入 ResourceCache 统一管理;RenderTargetProxy::getStencil 经 DepthStencilTextureView::Make 获取共享 attachment(维持 getStencil + getOrAllocateStencil 双层结构,TextureRenderTargetProxy 继续以 backingStore 尺寸定制),OpsRenderTask 通过 getStencil 取用并以 getTexture 填入 render pass descriptor。stencil 仅作为 unique 资源复用,不挂 scratch key、不参与 unique→scratch 降级;共享安全性依赖各 render pass 的 LoadAction::Clear,内容不跨 pass 携带。
效果:同规格的多个 RenderTarget 共享同一块物理 stencil,显存占用从每 proxy 一块降为每规格一块。
测试:RenderTargetProxyGetStencil 覆盖 proxy 内复用、跨 proxy 共享与 spec 隔离三个场景;Dispatch_StencilReuseAcrossPasses 以多 render pass 复用加 EvenOdd 重绘验证 per-pass 清零(新增基准 StencilCoverPath/ReuseOverpasses 与 ReuseOverpasses_Redraw)。OpenGL 后端全量 594 个用例通过。