filters와 blend modes가 batch를 깨는 이유
디자인 도구에는 blur, shadow, multiply, screen 같은 효과가 필요합니다. 하지만 이 효과들은 renderer의 batch와 pass 구조를 흔듭니다.
effect가 render pass를 만드는 코드
filter가 있는 subtree는 먼저 texture에 그리고, filter shader를 적용한 뒤 화면에 합성합니다.
function renderNodeWithEffects(renderer, node) {
if (!node.effects?.length && node.blendMode === "normal") {
renderer.drawNodeDirect(node);
return;
}
const target = renderer.acquireRenderTarget(node.worldBounds);
renderer.pushRenderTarget(target);
renderer.clearTransparent();
renderer.drawSubtree(node);
renderer.popRenderTarget();
for (const effect of node.effects) {
renderer.applyFilterPass(target, effect);
}
renderer.compositeRenderTarget(target, node.blendMode);
renderer.releaseRenderTarget(target);
}
batch가 깨지는 순간
same shader + same texture + same blend -> one batch
blend changed -> new batch
filter applied -> render target pass
filter는 보통 대상 subtree를 texture에 먼저 그리고, 그 texture에 shader를 적용한 뒤 다시 합성합니다.
pass graph로 effect 비용을 드러낸다
function buildPassesForNode(node) {
const passes = [];
if (node.effects?.length) {
passes.push({ type: "render-subtree-to-target", nodeId: node.id });
for (const effect of node.effects) {
passes.push({ type: "filter", effect, source: "previous-target" });
}
passes.push({ type: "composite-target", blendMode: node.blendMode ?? "normal" });
return passes;
}
passes.push({ type: "direct-draw", nodeId: node.id, blendMode: node.blendMode ?? "normal" });
return passes;
}
effect가 많아지면 draw call만 늘어나는 것이 아니라 render target 전환과 intermediate texture가 늘어납니다. pass graph를 로그로 남기면 어느 효과가 비용을 만드는지 보입니다.
오늘의 핵심
effect는 문서 모델에서는 속성이지만 renderer에서는 pipeline 변화입니다. 비용 모델 없이 넣으면 성능 문제가 바로 드러납니다.