Skip to content

feat(osr): smooth, never-stuck tile resize via begin-frame pump + GPU blit#8

Merged
wenkaifan0720 merged 1 commit into
mainfrom
feat/osr-resize-begin-frame-gpu-blit
Jun 24, 2026
Merged

feat(osr): smooth, never-stuck tile resize via begin-frame pump + GPU blit#8
wenkaifan0720 merged 1 commit into
mainfrom
feat/osr-resize-begin-frame-gpu-blit

Conversation

@wenkaifan0720

Copy link
Copy Markdown
Collaborator

What

Makes OSR webview tiles resize like a native browser instead of freezing or stuttering. Three problems, fixed in the present/resize path:

  1. Stuck resize — CEF's internal damage-driven scheduler can skip the post-resize frame (a resize is viewport-only damage on an idle page), so the tile stayed at the old size until a scroll forced a tick. Fix: own the frame clock — external_begin_frame_enabled + a per-slot SendExternalBeginFrame pump (idles while hidden, dies on dispose).
  2. Choppy / wedged reflow — resize flow-control (one in flight, send next on present) paces to cef_host's actual rate instead of racing ahead and stranding presents on superseded surface ids. A generation-tagged watchdog re-kicks a wedged step and, worst case, force-shows the already-painted surface.
  3. Flash on resize — pending-surface promotion serves the old surface until the new one paints.

Plus: the per-frame present copy is now a GPU Metal blit instead of a CPU IOSurfaceLock+memcpy — keeps frame data on-GPU end-to-end (no readback). Popups and the software-OSR fallback stay CPU.

Why not true zero-copy

CEF's pool contract (cef_render_handler.h) reclaims the accelerated surface at callback return, so handing it straight to Flutter is impossible — a copy into a client-owned texture inside the callback is mandated on every platform. So we make that copy a GPU blit, which is also the contract-mandated cross-platform shape (Windows D3D11 shared-handle, Linux dmabuf).

Test

Verified in the example app on M4 Pro: loads, resizes smoothly with no stuck/flash on flutter.dev (static) and YouTube (video), <select> dropdown still correct (CPU path), GPU-blit path confirmed active via the host log.

🤖 Generated with Claude Code

… blit

Reworks the OSR present/resize path so webview tiles resize like a native
browser instead of freezing or stuttering.

- Own the frame clock: window_info.external_begin_frame_enabled + a per-slot
  SendExternalBeginFrame pump. CEF's internal damage-driven scheduler could
  skip the post-resize frame (a resize is viewport-only damage on an idle
  page), leaving the tile stuck at the old size until a scroll forced a tick.
  Driving begin-frames ourselves makes every resize produce a frame. The pump
  idles to a slow poll while the tile is hidden and dies when it's disposed.

- Resize flow-control + watchdog (CefWebSession): keep one resize in flight,
  send the next when its present promotes, so the reflow paces to cef_host's
  actual rate instead of racing ahead and stranding presents on superseded
  surface ids (which froze the texture mid-drag). A generation-tagged watchdog
  re-kicks a wedged step and, worst case, force-shows the already-painted
  surface -- no permanent stuck on a static page's single post-resize frame.

- Pending-surface promotion: serve the old surface to Flutter until cef_host
  paints the new one, so a resize no longer flashes blank.

- GPU Metal blit present: copy CEF's accelerated IOSurface into the host
  surface with a Metal blit instead of a CPU IOSurfaceLock+memcpy. Keeps frame
  data on the GPU end-to-end (no GPU->CPU readback) -- a real win on
  discrete-GPU / Windows / Linux, ~neutral on unified-memory Apple Silicon, and
  the contract-mandated cross-platform shape (see the PORTING note on
  CompositeMetalLocked). Popups and the software-OSR OnPaint fallback stay on
  the CPU path; falls back to CPU if Metal is unavailable.

- Dart resizes on every layout change now that the native side paces.

True zero-copy (handing CEF's surface straight to Flutter) is impossible: CEF's
pool contract reclaims the accelerated surface at callback return, so a copy
into a client-owned texture inside the callback is mandated on every platform.
This makes that copy a GPU blit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wenkaifan0720 wenkaifan0720 merged commit a468475 into main Jun 24, 2026
1 check passed
@wenkaifan0720 wenkaifan0720 deleted the feat/osr-resize-begin-frame-gpu-blit branch June 24, 2026 02:23
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.

1 participant