Skip to content

perf(web): render membrane nebula at 1/4 resolution to kill load jank#29

Merged
dannyna merged 3 commits into
mainfrom
perf/web-membrane-nebula-lowres
Jul 15, 2026
Merged

perf(web): render membrane nebula at 1/4 resolution to kill load jank#29
dannyna merged 3 commits into
mainfrom
perf/web-membrane-nebula-lowres

Conversation

@dannyna

@dannyna dannyna commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

배경

랜딩페이지 접속 초반에 간헐적 jank가 있었습니다. 원인을 추정이 아닌 측정으로 특정했습니다.

useMembrane의 고정 배경 캔버스가 화면 크기의 라디얼 글로우 구름 6개를 매 프레임 메인 캔버스에 직접 fill 하고 있었습니다. 래스터를 강제한 마이크로벤치마크(2560×1237)에서 프레임당 비용은 다음과 같았습니다:

op 프레임당 비용
은하 nebula 라디얼 fill ×6 ~21ms 🔴
shadowBlur 문틀 글로우 ×2 ~0.9ms
별 352개 arc ~0.37ms

nebula 하나가 16.7ms 프레임 예산을 혼자 초과했습니다. 비용은 gradient 생성이 아니라 화면 크기 fill 오버드로우의 래스터화라, gradient 캐싱은 무효였습니다. 이게 로드 초반 무거운 첫 프레임들 = 초기 jank의 실체입니다.

변경

nebula를 1/4 해상도 오프스크린 캔버스에 렌더 후 업스케일 blit. 구름이 본디 흐릿해 업스케일해도 시각적으로 동일하고, 드리프트·펄스는 매 프레임 그대로 유지됩니다.

  • 21.2ms → 0.28ms/frame (~77배)
  • 별·shadowBlur는 부차적임을 확인하고 미변경
  • 타입체크·프로덕션 빌드·시각 동일성(스크린샷) 확인

검증 노트

op 비용 감소는 벤치마크로 확실합니다. 다만 체감 jank 해소는 라이브 확인 필요 — 벤치마크에 쓴 백그라운드 자동화 탭에서는 rAF가 정지되어 실제 FPS를 못 잽니다. 리뷰어가 포그라운드에서 reload 몇 번 / DevTools Performance로 확인 부탁드립니다.

🤖 Generated with Claude Code


Summary by cubic

Render the membrane nebula at 1/4 resolution on an offscreen canvas and upscale each frame to remove load jank. Add a zero-size guard to avoid Safari IndexSizeError on the first frame; drift/pulse stay live; cost ~21.2ms → ~0.28ms/frame (~77x); stars and shadowBlur unchanged. CI retriggered for fresh CodSpeed baseline (no code changes).

Written for commit b255bfb. Summary will update on new commits.

The fixed background canvas painted 6 screen-sized radial-gradient glow
clouds every frame directly to the main canvas. A forced-raster
microbenchmark at 2560x1237 measured this at ~21ms/frame — on its own
over the 16.7ms frame budget — making it the dominant cost of the heavy
first animation frames that produce the intermittent startup jank.

The clouds are inherently soft, so render them to a 1/4-resolution
offscreen canvas and upscale-blit once per frame. Drift and pulse stay
fully live; only the fill overdraw shrinks. Measured 21.2ms -> 0.28ms
per frame (~77x). Stars (0.37ms) and the frame shadowBlur (0.9ms) were
confirmed minor and left unchanged.

Note: felt jank must be confirmed live (rAF is suspended in the
background automation tab used to benchmark), but the dominant per-frame
op cost is verified to drop dramatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 30.7%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 (👁 1) regressed benchmark
✅ 16 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
👁 parse_k8s_path[/api/v1/namespaces/prod/secrets/db-password] 5.9 µs 8.5 µs -30.7%

Comparing perf/web-membrane-nebula-lowres (b255bfb) with main (c078133)

Open in CodSpeed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request optimizes the rendering of the galaxy gas layer in useMembrane.ts by drawing it onto a 1/4 scale offscreen canvas and upscaling it, which reduces fill rate overhead. The review feedback correctly points out a potential IndexSizeError in some browsers if drawImage is called with dimensions of 0 before the first resize occurs, and suggests a guard condition to prevent this.

Comment thread apps/web/src/hooks/useMembrane.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

Architecture diagram
sequenceDiagram
    participant RAF as requestAnimationFrame
    participant CM as Canvas Manager (useMembrane)
    participant OC as Offscreen Canvas (nebCanvas)
    participant MC as Main Canvas
    participant Browser as Browser Compositor

    Note over RAF,Browser: Per-frame render pipeline

    loop Every animation frame
        RAF->>CM: frame callback (timestamp)
        CM->>CM: resize check & DPR transform
        
        CM->>CM: star rendering (unchanged)
        Note over CM: Stars: 0.37ms
        
        CM->>CM: shadowBlur glow (unchanged)
        Note over CM: shadowBlur: 0.9ms

        CM->>OC: nebula rendering
        
        Note over CM,OC: 1/4 resolution offscreen path
        
        CM->>OC: clearRect (0,0,nebW,nebH)
        
        loop For each of 6 nebula clouds
            CM->>OC: update drift position (x+=dx, y+=dy)
            CM->>OC: calculate alpha (pulse wave)
            CM->>OC: createRadialGradient (sx,sy,sr)
            CM->>OC: fillStyle = gradient
            CM->>OC: arc + fill()
        end
        
        Note over CM,OC: 0.28ms total (was 21.2ms at full res)

        CM->>MC: drawImage(nebCanvas, 0,0 → 0,0,W,H)
        Note over CM,MC: Upscale blit to main canvas
        
        MC-->>Browser: composite frame
        Browser-->>RAF: next frame ready
    end

    alt Initial load (first frames)
        Note over CM,MC: Heavy gradient rasterization is now on small offscreen canvas
        Note over MC,Browser: Main canvas paint is cheap (scaled blit)
    end
Loading

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR moves the membrane nebula rendering onto a smaller offscreen canvas.

  • Adds a quarter-resolution canvas for the nebula glow layer.
  • Resizes the offscreen buffer with the main canvas.
  • Draws the scaled nebula back onto the main canvas each frame.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/web/src/hooks/useMembrane.ts Adds offscreen nebula rendering and buffer resizing for the membrane background.

Reviews (3): Last reviewed commit: "chore: retrigger CI for fresh CodSpeed b..." | Re-trigger Greptile

dannyna and others added 2 commits July 15, 2026 10:49
Belt-and-suspenders: skip the offscreen render + drawImage when the
nebula buffer dimensions are 0, so no IndexSizeError can fire on the
first frame before resize() runs (Safari throws on a zero-size source
rect). Addresses PR review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CodSpeed flagged a phantom -32% regression on an unrelated Rust
benchmark (parse_k8s_path) though this branch changes only frontend TS.
The first commit's CodSpeed run passed; forcing a fresh measurement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@dannyna dannyna linked an issue Jul 15, 2026 that may be closed by this pull request
4 tasks
@dannyna dannyna merged commit 84e340c into main Jul 15, 2026
12 checks passed
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.

Landing page: jank (stutter) during scroll/animation

1 participant