MMaarten Vroegindeweijperf(pdf): avoid capped whole-page rerenders on HiDPI
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
docs: vector rendering implementation plan + MCP server test tools | 5 个月前 | |
chore: add render-engine benchmark scaffolding src-tauri/src/lib.rs New Tauri command render_pdf_page_skia — invokes our open-pdf-render Skia kernel directly for side-by-side timing vs PDFium's render_pdf_page. Same wire format ([w:u32 LE][h:u32 LE][rgba…]) so probes can swap one for the other without code changes. NOT wired into the live render path — production stays on PDFium until open-pdf-render passes the < 2 % pixel-diff goal in scripts/render-regression-test.py. mcp-server/skia-vs-pdfium-render.mjs (new) Side-by-side speed probe. For each PDF + page: cold + warm timings for both engines. Findings on current branch (b88853fb..HEAD): - Skia wins decisively on clean vector pages (NKE2D2 p1: 7.8x; rapport p1: 3.4x faster cold than PDFium) - PDFium wins on text-heavy (Tekst.pdf: 9.5x) and raster-heavy (BARN p1: 2.2x) pages - Both ~equal on extreme cases (NKD1a multi-MB pages, ~2.5 s cold) mcp-server/pdfjs-vs-rust-render.mjs (new) Same pattern, comparing PDF.js's own canvas render to our Rust path. Result: Rust 2-6x faster on construction PDFs, PDF.js only wins on near-empty cover pages (~3 ms vs ~60 ms IPC overhead). mcp-server/engine-comparison.mjs, open-perf-probe.mjs, decode-screenshot.mjs (new) Helper probes used during the v1.51 / v1.52 perf work. | 3 个月前 | |
chore: add render-engine benchmark scaffolding src-tauri/src/lib.rs New Tauri command render_pdf_page_skia — invokes our open-pdf-render Skia kernel directly for side-by-side timing vs PDFium's render_pdf_page. Same wire format ([w:u32 LE][h:u32 LE][rgba…]) so probes can swap one for the other without code changes. NOT wired into the live render path — production stays on PDFium until open-pdf-render passes the < 2 % pixel-diff goal in scripts/render-regression-test.py. mcp-server/skia-vs-pdfium-render.mjs (new) Side-by-side speed probe. For each PDF + page: cold + warm timings for both engines. Findings on current branch (b88853fb..HEAD): - Skia wins decisively on clean vector pages (NKE2D2 p1: 7.8x; rapport p1: 3.4x faster cold than PDFium) - PDFium wins on text-heavy (Tekst.pdf: 9.5x) and raster-heavy (BARN p1: 2.2x) pages - Both ~equal on extreme cases (NKD1a multi-MB pages, ~2.5 s cold) mcp-server/pdfjs-vs-rust-render.mjs (new) Same pattern, comparing PDF.js's own canvas render to our Rust path. Result: Rust 2-6x faster on construction PDFs, PDF.js only wins on near-empty cover pages (~3 ms vs ~60 ms IPC overhead). mcp-server/engine-comparison.mjs, open-perf-probe.mjs, decode-screenshot.mjs (new) Helper probes used during the v1.51 / v1.52 perf work. | 3 个月前 | |
feat: 1.58.0 — engine selector + fix thumbnail/prefetch contention js/solid/components/StatusBar.jsx + js/core/state.ts New state.renderEngineOverride: 'pdfium' | 'rust-skia' | null. The bottom-right render-engine badge is now a clickable cycler: Auto → PDFium (forced) → Rust (alpha) → Auto Click triggers a re-render of the current page via the chosen engine. Orange chip color when Rust (alpha) is active to make the alpha state obvious — open-pdf-render is < the 2 % pixel-diff goal vs PyMuPDF reference (per scripts/render-regression-test.py), so the rendered page may differ from PDFium's output. js/pdf/page-bitmap-cache.js ensureBitmap dispatches to render_pdf_page or render_pdf_page_skia based on state.renderEngineOverride. Default routing stays on render_pdf_page (PDFium), so existing flows are unaffected. js/pdf/loader.js (revert tile-page prefetch) v1.55-v1.56 introduced a background prefetch that warmed tile-page bitmaps at scale=0.125–0.25. Real-world testing showed: • PDFium serializes via global mutex → prefetch blocks the thumbnail-processor's render_thumbnail calls for 9-20 s on construction PDFs (NKD1a). User reported "thumbnails weer trager". • The orchestrator's exact-bucket render still ran AFTER the blurry fallback paint (2.7 s for NKD1a fit-zoom), so the user still waited for the crisp upgrade. • Net win was just "blurry instead of blank" on the brief first paint — not worth the thumbnail regression. Removed. Thumbnails now own PDFium during cold-open. First nav to a tile page pays the ~2.7 s cold-render cost ONCE; subsequent navigations hit the Rust pixmap cache and are ~150 ms. Multi-process PDFium remains the proper fix for the cold-render latency on huge construction-PDF pages. mcp-server/live-perf-monitor.mjs (new) Streams [PERF] / [render] / [thumb] / [tile] / [bitmap-orch] events from the live app in real time. Each event becomes a separate notification — useful for observing user interactions as they happen during a manual test session. | 3 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
test(perf): multi-process probe for NKD1a 4-page sequential Measures the spec's primary success criterion: 4 sequential page renders complete in ≤ 1800 ms (vs ~6000 ms current baseline). | 3 个月前 | |
fix: 1.58.1 — line/stroke visibility at zoom < 100 % js/annotations/rendering.js (thinLw) Enforce a 1-screen-pixel minimum stroke width when the active viewport zoom is below 100 %. A 1 pt app-space stroke at e.g. 35 % zoom resolves to ~0.35 screen-pixels and the canvas anti-aliases that into a near-invisible ghost — the user-visible cause of the "Line doesn't appear when drawn via Tekenen" report. Filled tools (Rectangle/Circle/ Cloud) stayed visible via their fill; outline-only tools (Line/Arrow/ Polyline/Box-no-fill/Circle-no-fill) were the ones disappearing. At zoom >= 100 % behavior is unchanged. Thin-lines preference still clamps to 1 px max. Width === 0 still means "no border" per spec. mcp-server/nkd1a-fullmeasure.mjs (new) Comprehensive NKD1a perf probe used to baseline v1.58.0 (cold open + cold/warm per-page nav across all 7 pages). Numbers feeding the v1.59 multi-process PDFium design. | 3 个月前 | |
chore: add render-engine benchmark scaffolding src-tauri/src/lib.rs New Tauri command render_pdf_page_skia — invokes our open-pdf-render Skia kernel directly for side-by-side timing vs PDFium's render_pdf_page. Same wire format ([w:u32 LE][h:u32 LE][rgba…]) so probes can swap one for the other without code changes. NOT wired into the live render path — production stays on PDFium until open-pdf-render passes the < 2 % pixel-diff goal in scripts/render-regression-test.py. mcp-server/skia-vs-pdfium-render.mjs (new) Side-by-side speed probe. For each PDF + page: cold + warm timings for both engines. Findings on current branch (b88853fb..HEAD): - Skia wins decisively on clean vector pages (NKE2D2 p1: 7.8x; rapport p1: 3.4x faster cold than PDFium) - PDFium wins on text-heavy (Tekst.pdf: 9.5x) and raster-heavy (BARN p1: 2.2x) pages - Both ~equal on extreme cases (NKD1a multi-MB pages, ~2.5 s cold) mcp-server/pdfjs-vs-rust-render.mjs (new) Same pattern, comparing PDF.js's own canvas render to our Rust path. Result: Rust 2-6x faster on construction PDFs, PDF.js only wins on near-empty cover pages (~3 ms vs ~60 ms IPC overhead). mcp-server/engine-comparison.mjs, open-perf-probe.mjs, decode-screenshot.mjs (new) Helper probes used during the v1.51 / v1.52 perf work. | 3 个月前 | |
chore: add render-engine benchmark scaffolding src-tauri/src/lib.rs New Tauri command render_pdf_page_skia — invokes our open-pdf-render Skia kernel directly for side-by-side timing vs PDFium's render_pdf_page. Same wire format ([w:u32 LE][h:u32 LE][rgba…]) so probes can swap one for the other without code changes. NOT wired into the live render path — production stays on PDFium until open-pdf-render passes the < 2 % pixel-diff goal in scripts/render-regression-test.py. mcp-server/skia-vs-pdfium-render.mjs (new) Side-by-side speed probe. For each PDF + page: cold + warm timings for both engines. Findings on current branch (b88853fb..HEAD): - Skia wins decisively on clean vector pages (NKE2D2 p1: 7.8x; rapport p1: 3.4x faster cold than PDFium) - PDFium wins on text-heavy (Tekst.pdf: 9.5x) and raster-heavy (BARN p1: 2.2x) pages - Both ~equal on extreme cases (NKD1a multi-MB pages, ~2.5 s cold) mcp-server/pdfjs-vs-rust-render.mjs (new) Same pattern, comparing PDF.js's own canvas render to our Rust path. Result: Rust 2-6x faster on construction PDFs, PDF.js only wins on near-empty cover pages (~3 ms vs ~60 ms IPC overhead). mcp-server/engine-comparison.mjs, open-perf-probe.mjs, decode-screenshot.mjs (new) Helper probes used during the v1.51 / v1.52 perf work. | 3 个月前 | |
chore: add render-engine benchmark scaffolding src-tauri/src/lib.rs New Tauri command render_pdf_page_skia — invokes our open-pdf-render Skia kernel directly for side-by-side timing vs PDFium's render_pdf_page. Same wire format ([w:u32 LE][h:u32 LE][rgba…]) so probes can swap one for the other without code changes. NOT wired into the live render path — production stays on PDFium until open-pdf-render passes the < 2 % pixel-diff goal in scripts/render-regression-test.py. mcp-server/skia-vs-pdfium-render.mjs (new) Side-by-side speed probe. For each PDF + page: cold + warm timings for both engines. Findings on current branch (b88853fb..HEAD): - Skia wins decisively on clean vector pages (NKE2D2 p1: 7.8x; rapport p1: 3.4x faster cold than PDFium) - PDFium wins on text-heavy (Tekst.pdf: 9.5x) and raster-heavy (BARN p1: 2.2x) pages - Both ~equal on extreme cases (NKD1a multi-MB pages, ~2.5 s cold) mcp-server/pdfjs-vs-rust-render.mjs (new) Same pattern, comparing PDF.js's own canvas render to our Rust path. Result: Rust 2-6x faster on construction PDFs, PDF.js only wins on near-empty cover pages (~3 ms vs ~60 ms IPC overhead). mcp-server/engine-comparison.mjs, open-perf-probe.mjs, decode-screenshot.mjs (new) Helper probes used during the v1.51 / v1.52 perf work. | 3 个月前 | |
diag(thumb): instrument JS-replay path with [thumb] console logs Adds diagnostic logging in renderThumbnailToDataURL JS-replay path so we can probe cached page dims and rendered canvas size via the MCP console-ring buffer. Extends mcp-bridge.js CONSOLE_CAPTURE_RE to capture [thumb], [bitmap-orch], [tile-orch] in addition to existing prefixes. Adds mcp-server/sweep-all-pdfs.mjs harness that opens every PDF in the test corpus, navigates pages, and runs zoom-anchor probes. | 3 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
test: wait for initial vector render before zooming | 1 个月前 | |
perf(pdf): avoid capped whole-page rerenders on HiDPI | 1 个月前 | |
perf(pdf): avoid capped whole-page rerenders on HiDPI | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
perf: 1.53.0 — rapid zoom freeze + JS analyze cache + size threshold 500 KB js/pdf/pdf-viewport.js — ZOOM-FREEZE On the first zoom step in a burst, snapshot the current canvas pixels to an OffscreenCanvas. During the next 150 ms (extended by every subsequent step), _render() short-circuits to drawImage the snapshot stretched to the new (zoom, offset) transform — skipping clamp + white background + renderVectorPage entirely. After 150 ms of zoom stillness, drop the snapshot and trigger ONE fresh full render at the final zoom level. User experience: 8 rapid zoom-in clicks now render in ~73 ms total (vector pages) and ~140 ms (raster pages) instead of 8 sequential full repaints. The visible page rescales smoothly with each click; the crisp final render settles 150 ms after the last click. Annotation overlay redraw is still called in the freeze path so annotations track the stretching page bitmap instead of "sticking". js/pdf/page-type-cache.js (new) + loader.js + renderer.js JS-side mirror of the Rust PageTypeCache. The Rust cache makes warm analyze_page_type invokes return in microseconds, BUT during cold-open the IPC queue is saturated by thumbnail invokes (~28 per multi-page doc) and a single analyze invoke can wait 1.5 s queued behind them even though Rust completes in <1 ms. analyze_page_type_batch (already populating the Rust cache) now also populates this JS cache. renderer.js consults it BEFORE invoking, bypassing IPC entirely on cache hit. Measured fix on rapport-constructie.pdf p26 cold nav: 2335 ms (full lopdf decode + queued IPC) -> 172 ms (JS-cache hit, vector commands path). 13.5x faster. open-pdf-render/src/parser.rs TILE_CONTENT_THRESHOLD_BYTES lowered 1 MB -> 500 KB. Catches the outlier vector pages (500 KB - 1 MB content streams) that would otherwise hit the slow lopdf Content::decode path. Pages with smaller content streams stay on the JS-replay vector path; pages >500 KB skip the decode and classify Tile so PDFium raster renders them in ~200 ms instead of waiting 500-1500 ms for the operator decoder. mcp-server/zoom-debounce-test.mjs (new) Probe that runs the 8-rapid-zoom test on any PDF + page. Reports total IPC time and the __zoomFreezeFrameCount counter so the freeze fast-path activity is observable. | 3 个月前 | |
fix(mcp): zoom-loop URL needs /mcp path; doc OCG limitation in renderer mcp-server/zoom-loop.mjs: MCP_URL was http://127.0.0.1:9223/ but the server now mounts at /mcp; fix URL so the harness reaches it. src-tauri/src/pdfium_renderer.rs: document the OCG-layer rendering limitation discovered during NKE2D2_opm_aw.pdf testing. PDFs with PDF-XChange editor-only markup overlays render those overlays VISIBLE because pdfium-render 0.9.1 does not expose FPDFOCG_IsContentVisible. Edge and PDF-XChange Editor both filter these layers correctly. Filed as v1.51 follow-up: add custom FFI calls to query+apply OCG visibility before rendering. Config itself reverted to defaults — diagnostic print-mode + no-annotation experiments were not the right fix and removed user-visible annotations on every other PDF. | 3 个月前 | |
refactor: remove tile DOM canvas The <canvas id='pdf-canvas-tile'> element is no longer needed - the tile augment is now a second drawImage() call in pdf-viewport.js's unified render loop (Task 3). Removed: - The canvas element from App.jsx and MobileApp.jsx - The #pdf-canvas-tile CSS ruleset (and its comment block) in layout.css - All JS references in mcp-bridge.js (state capture now reads viewport.currentTile / currentTileMeta from the singleton) - Same change applied to mcp-server/zoom-observer.mjs probe so the CDP observer keeps reporting tile state correctly | 3 个月前 | |
publish accumulated editor and annotation improvements (#309) | 1 个月前 | |
fix(zoom): stale-gen check also applies to JS-cache HIT path (third fix) User report: zoom STILL hopping after the first two gen-counter fixes (6fa3613e renderPage cache-miss path, 3ad296a8 wheel-handler scroll adjustment). CDP probe with REAL WheelEvents (mcp-server/zoom-wheel-probe.mjs) reproduced the symptom and captured the missing case: when rapid ctrl+wheel events fire, N renderPage() calls each await analyze_page_type (serialized ~1s on the PDFium font_registry mutex). After analyze_page_type returns, each renderPage looks up its scale in the JS bitmap cache. Pre-render-adjacent had warmed scales 1.25, 1.5, 1.75, 2.0 — so renderPages for those scales each got a CACHE HIT and painted their respective bitmap to canvas. The CACHE HIT code path had NO stale-gen check, so all four stale renders' bitmaps painted in sequence over ~50 ms, producing the visible 'page hops through resolutions' UX bug. Trace from zoom-wheel-probe.mjs (BURST: 5 wheel events at 30ms intervals, expected scale 1.0 -> 2.25): +1269ms: JS-cache HIT 3060x1980 (scale 1.25 — STALE, was painted) +1282ms: JS-cache HIT 3672x2376 (scale 1.5 — STALE, was painted) +1295ms: JS-cache HIT 4284x2772 (scale 1.75 — STALE, was painted) +1320ms: JS-cache HIT 4896x3168 (scale 2.0 — STALE, was painted) +1341ms: Rust render scale=2.25 (LATEST, the only correct one) +3172ms: Rust render OK 5508x3564 -> canvas Fix: in the JS-cache HIT block (renderer.js ~L484), gate the canvas writes on _isStaleGen(). Stale cache hits set _skipBitmapRender=true (so the Rust path also doesn't run) but DON'T touch the canvas. This is the third gen-counter fix in a row. The pattern: any code path that mutates the shared #pdf-canvas after an async boundary must check the gen counter. Future refactor: extract a helper that all three paths use. Also commits the CDP probe so future investigations have a known- good reproducer for the rapid-zoom race. | 3 个月前 |
Open PDF Studio — Testing MCP Server
CDP-based test tools for automating the Tauri desktop app.
Prerequisites
-
Start the app with CDP enabled:
export WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS="--remote-debugging-port=9222" cd open-pdf-studio && npm run tauri:dev -
Install Playwright:
cd open-pdf-studio && npm install playwright
Test Scripts
| Script | Description |
|---|---|
test-tauri-cdp.mjs |
Full E2E test: open PDF, render, zoom, screenshot |
test-symbols-cdp.mjs |
Symbol placement test |
test-real-pdf.mjs |
MuPDF WASM rendering test (browser-only) |
Usage
cd open-pdf-studio
node mcp-server/test-tauri-cdp.mjs