dart_pdf_editor_flutter_gpu 0.3.3 copy "dart_pdf_editor_flutter_gpu: ^0.3.3" to clipboard
dart_pdf_editor_flutter_gpu: ^0.3.3 copied to clipboard

Opt-in Impeller flutter_gpu tile backend for dart_pdf_editor.

dart_pdf_editor_flutter_gpu #

Opt-in retained-scene tile backend for dart_pdf_editor. It compiles supported PDF commands and uploads decoded images once per retained scene, then renders LoD tile slabs directly with Impeller's package:flutter_gpu API.

This is an experimental 0.x companion package: its public API may evolve as Flutter's GPU API matures. Unsupported content and platforms fall back to the stable Canvas renderer instead of approximating PDF output.

flutter pub add dart_pdf_editor dart_pdf_editor_flutter_gpu

This companion deliberately stays outside dart_pdf_editor's dependency graph. The core viewer keeps its Flutter 3.24 minimum and Canvas/web behavior; apps that choose this backend use Flutter 3.44 or newer and pass it explicitly:

PdfReader(
  bytes: bytes,
  // Keep one instance: its byte-budgeted image textures are reusable across
  // retained scenes, pages, workers, and LoDs.
  tileRasterBackend: FlutterGpuTileRasterBackend(
    maxTextureBytes: 256 << 20,
    maxGeometryBytes: 256 << 20,
    // Ordinary filled outlines use a scale-independent curve atlas by
    // default. Sparse native substitution runs stay on cheaper retained
    // stencil geometry; dense text still amortizes the atlas setup.
    analyticText: true,
    // Optional: retain simple substituted text using the native faces that
    // Canvas normally selects. Leave false if the app registers replacements
    // under these family names.
    systemTextOutlines: true,
  ),
)

Unembedded text remains on the conservative Canvas fallback by default because Flutter does not expose the glyph paths selected by TextPainter. A host that owns its font registrations can pass a FlutterGpuTrueTypeTextOutliner whose resolver returns FlutterGpuFontFace instances built from those exact bytes, including FlutterGpuTrueTypeFontFace and FlutterGpuOpenTypeCffFontFace. systemTextOutlines: true is the native convenience adapter: it probes known Helvetica/Arial, Times, Courier, Symbol, and platform-equivalent font files. On macOS it also resolves the exact Songti, Heiti, Hiragino Sans, and Hiragino Mincho faces selected by the Canvas CJK substitution stack, including OpenType CFF faces inside font collections. It declines a run when the requested face, glyph, or simple horizontal placement cannot be proved. It is deliberately opt-in because an app may have registered different bytes under the same Flutter family name. Web keeps the Canvas path.

Flutter GPU must also be enabled by the host. Add <key>FLTEnableFlutterGPU</key><true/> to the iOS/macOS Info.plist (and FLTEnableImpeller where Impeller is not already the platform default), or this inside the Android <application> element:

<meta-data
  android:name="io.flutter.embedding.android.EnableFlutterGPU"
  android:value="true" />

The retained backend currently activates only on Metal and Vulkan contexts. Flutter 3.47's OpenGLES flutter_gpu pipeline path can terminate the process before Dart receives an error, so the backend detects the GLES capability set before creating shaders and uses the exact Canvas fallback instead. Android hosts do not need to force Vulkan: capable devices normally select it through Impeller, while GLES-only devices remain safe on Canvas.

For a desktop development launch, pass both engine opt-ins:

flutter run -d windows --enable-impeller --enable-flutter-gpu
flutter run -d linux --enable-impeller --enable-flutter-gpu

Packaged desktop applications must arrange the equivalent engine settings before the engine starts. The DartPDF PR preview workflow demonstrates this with profile-mode Windows/Linux bundles and also publishes a macOS DMG.

No master SDK, native-assets hook, or runtime shader compiler is required. The Metal and Vulkan runtime stages are compiled offline and checked in as a package asset. Unsupported platforms or contexts, disabled contexts, and unsupported PDF features return to the Canvas tile backend automatically. Web gets a compile-time stub and therefore preserves the all-platform host surface.

The current exact subset is solid paths and strokes (including zero-width PDF hairlines that stay one device pixel at every LoD), filled and stroked embedded-outline text, simple substituted text when an exact FlutterGpuTextOutliner resolves it, decoded images/image masks, Gouraud meshes, axial gradients, nested-circle radial gradients, vector and stencil-image tiling cells, normal/Multiply/Screen blending, solid-black overprint (including inside transparency groups), and exact destination-sampling Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, and Luminosity blending, rectangular and arbitrary path clips, and the common isolated single-image soft-mask group. Isolated transparency groups containing a single vector fill, stroke, outlined text run, ordinary image, gradient, or Gouraud mesh also stay on the GPU: their group alpha and page blend mode are retained exactly. One-element knockout groups are included because there are no sibling elements for knockout to change. Alpha-one, normal-blend non-knockout groups may contain multiple ordered fills, strokes, outlined text runs, images, gradients, and meshes because source-over is associative, so their isolation layer is an identity operation. Nested alpha-one identity groups flatten into the same retained parent while keeping their distinct per-paint clips. Isolated overlapping groups retain those same paint types in the bounded offscreen tile pass before applying group alpha and the outer blend once. A nested one-image group with a transparent backdrop and Normal blend folds its group alpha into that image before joining its retained parent; the equivalent premultiplied source avoids a redundant nested pass. An alpha-one nested knockout group with a uniform declared backdrop can also join an isolated parent: the backdrop becomes its first bounded paint and the following shapes preserve source replacement in the parent's attachment. Opaque non-isolated knockout groups at page level retain ordered vector fills and strokes the same way: their bounded attachment is seeded with that color and clipped to the form BBox. The intermediate group target stays single-sample while the final page target retains 4x MSAA, avoiding a redundant color/stencil raster and resolve. Every PDF blend mode remains per-paint state inside that attachment rather than being collapsed into the group's outer blend. Normal uses source-over directly; the other modes sample the possibly translucent group backdrop through bounded ping-pong attachments. Platform-decoded JPEGs whose /SMask remains a companion GPU surface also keep their base and mask as separate cached textures and combine them in the same shader path. A single vector fill can use one opaque grayscale image soft mask directly through retained stencil geometry. Rectangular vector soft-mask fills, including alpha or luminosity backdrops and linearized transfer functions, are partitioned into constant-mask stencil cover cells and need no intermediate texture. Positive-width vector strokes also retain image and axial-gradient soft masks; their dashed caps and joins become the same exact stencil geometry used by ordinary retained strokes before the mask is applied. Masked zero-width hairlines stay on Canvas because their one-device-pixel geometry is tile-scale dependent. An arbitrary content-side path clip around one soft-masked source is retained as the ordinary GPU stencil clip on the resolved composite. Arbitrary clips inside a single-image mask transcript use that same exact stencil when the mask's backdrop and transfer function make its outside value zero. Rectangular mask clips remain the bounded shader scissor; arbitrary mask clips with a non-zero outside value still use Canvas. The same composite stencil retains an arbitrary path clip around one fill, stroke, text run, image, gradient, or mesh inside a single-paint transparency group. An isolated multi-paint offscreen group preserves either a shared or a distinct arbitrary clip stack for every source paint before resolving the group alpha, preserving antialiased overlap coverage. Unisolated groups that need a page-backdrop-aware group result remain on Canvas. Ordinary image-, vector-, gradient-, and text-soft-masked sources may sit beside other retained paints inside a transparency group. Each mask resolves as one bounded parent paint before the enclosing group alpha is applied. The common opaque vector/text-mask stack also expands its base and nested image-masked paint directly into the parent while preserving the outer stencil and exact blend bounds. Enabled overprint and non-isolated groups that require the page backdrop remain on Canvas. Paints behind a degenerate or disjoint rectangular clip are discarded while capturing a transparency group, including balanced soft-mask content that can no longer affect a pixel. Save/restore still reinstates the prior clip for any later visible paint. Tiles that sit at least one device pixel inside the transformed crop box use the folded opaque paper color as their render-pass clear, avoiding a separate transparent clear and full-tile paper draw. Boundary tiles retain the paper quad so rotated and translucent page colors keep their exact antialiased edge. When spatial selection finds no retained command for one of those interior tiles, the backend submits a color-only clear pass. It does not allocate a stencil or multisample attachment, create a transient host buffer, or bind a draw pipeline; tiles near the crop edge continue through the ordinary path. Paint units whose clipped bounds are wholly outside the page /CropBox are discarded even earlier, before route compatibility checks and scene compilation. Off-page imposition marks therefore cannot reject the visible page or consume retained geometry. Advanced blend paints use bounded ping-pong tile attachments. Paints whose bounds prove they cannot affect one another share one destination-sampling pass; overlapping paints replay sequentially inside their conservative command bounds to preserve PDF painter order without shading unrelated tile pixels. Consecutive same-mode straight strokes can also share one transparent source and one blend when raster-space capsule tests prove that their resolved pixels are disjoint, even if their diagonal axis-aligned bounds overlap. When the conservative source union occupies at most half the tile, its transparent attachment is cropped to page-pixel-aligned bounds and the blend shader remaps those texels onto the unchanged full-sized backdrop. Thick offscreen strokes and low-LoD hairlines extend the crop before alignment, so the optimization cannot trim antialiasing coverage. Sparse sources also discard exactly transparent texels before the backdrop lookup and blend arithmetic. The target already contains a native copy of that destination, so skipping the fragment write produces the same PDF composite. Each pass preserves the untouched ping-pong destination with a byte-exact GPU texture blit rather than a full-tile fragment draw. Offscreen groups can precede or follow those paints in the same exact route: each group is prepared once in its bounded single-sample target, then sampled into the ping-pong page at its original painter-order position. The completed group texture can itself be the source of one advanced outer blend. The route rejects before allocating when its temporary attachments would exceed 256 MiB. Rectangles use hardware scissors; other clip stacks compile once into retained stencil geometry and preserve nonzero/even-odd plus save/restore semantics. The solid-fill path sends axis-aligned rectangles and non-degenerate closed triangles straight to the color pipeline. Longer polygons retain the stencil cover so internal triangulation edges cannot change multisample coverage. The mask case keeps the base and mask as two GPU textures and combines them during tile replay; it never builds an eager full-size RGBA composite or reads pixels back to the CPU. Worker-retained RGBA uploads directly; compatible locally platform-decoded images import their GPU texture directly into the shared cache without a pixel readback. For the uncommon tiled bitmap-font path, the local portable decoder keeps RGBA just long enough to upload the hand-built mip chain directly, then releases it. CPU-backed platform images retain the proven readback/upload fallback. A backend-wide byte-budgeted LRU preserves decoded texture identity across scenes, pages, workers, zoom levels, and LoDs. Pinned scene textures count toward the same ceiling; if no unpinned entry can make room, that scene falls back to Canvas instead of overshooting the budget. Compiled vertices share a second strict byte budget. They are packed into reusable power-of-two device-buffer size classes from 64 KiB through the 16 MiB arena chunk size, and returned to the backend-wide pool only after their scene is disposed and every submitted command buffer completes. This keeps sparse pages small and command-heavy CAD navigation bounded without relying on delayed native finalizers; a scene that cannot lease enough geometry also falls back.

Ordinary filled outline text uses retained Slug-style quadratic curve streams: one small nearest-sampled atlas stores each distinct page glyph and each draw retains only six vertices per placed glyph. The fragment shader derives its pixels-per-em from the current tile transform, so the same atlas stays sharp across LoDs and rotated text. Gradient and soft-masked text, malformed or over-complex outlines, and an atlas over the bounded 8 MiB ceiling retain the existing stencil-fan path. Atlas creation failure is likewise an optimization fallback, never a reason to reject the page.

After useful page pixels land and foreground work stays quiet for 750 ms, the viewer asks the backend to warm its context. The backend submits one transparent pixel through the common stencil, solid, and texture pipelines and the nonzero stencil-cover state used by retained fills. Page-specific glyph, soft-mask, and destination-sampling blend variants wait for the live scene warm-up below, so an ordinary document does not pay one uninterrupted compile for every optional path. This moves Impeller/driver compilation out of the first deep-zoom interaction without delaying initial document paint or competing with an immediate scroll. The common work is coalesced per native view and MSAA mode, even when several readers share the same process.

Proactive warm-up defaults on for macOS, Windows, and Linux. Vulkan Android and iOS stay on-demand by default because merely creating an Impeller GPU context can reserve significant memory before a page is known to be GPU-compatible. A host that has validated its mobile device range can opt in with enableProactiveWarmUp: true; normal on-demand GPU tiles remain available when the option is false. OpenGLES contexts skip warm-up and retain Canvas.

The same idle gate then prepares the live page's retained tile session: scene geometry and decoded-image uploads are compiled once and every scene pipeline is submitted at one-pixel page scale. The real first tile reuses those retained resources. Starting new foreground work cancels and restarts both delays; page disposal releases the prepared resources through the ordinary scene lifecycle.

backend.stats reports accepted/rejected/active sessions, the latest actual tile route, runtime fallback reasons, context and scene warm-up outcomes, scene compile and tile-submit time, spatially selected command counts, upload/readback paths, cache hits and evictions, budget fallbacks, retained bytes, and live resource leases. It also reports offCropUnitsCulled for unreachable paints removed before the route audit. Direct-primitive diagnostics count rectangle and triangle fills separately. Clip diagnostics separately report paths compiled and tile-mask rebuilds. Paper diagnostics report how many tiles used the exact interior clear path and how many of those were content-free color-only submissions. Transient-buffer diagnostics report emplaced bytes, allocated buffers/bytes, and the peak allocation for one tile, making dense dynamic hairline workloads and ordinary 64 KiB submissions separately visible. Subpixel-stroke diagnostics report resolution-aware runtime fallbacks for dense CAD tiles whose positive-width linework falls below one 4x MSAA coverage quantum; the viewer permanently serves that session through Canvas rather than displaying incomplete linework. Advanced-blend diagnostics report destination-sampling passes, destination blits, cropped-source selections, allocated and peak temporary bytes, and budget fallbacks. backend.stats.toJson() is suitable for benchmark artifacts. Keep the backend instance alive when comparing pages so those counters and cross-page caches describe the real workload rather than one page at a time.

Live GPU route devtool #

Turn on the viewer overlay while investigating a document:

pdfDebugShowGpuRasterRoutes.value = true;

Every page gets a screen-sized route badge and border. Green is an accelerated detail-tile session, amber is an exact Canvas fallback (with its rejection or runtime-failure reason), blue is an explicitly requested Canvas session, and grey means detail tiles have not been requested yet. The badge says detail tiles deliberately: the fitted base page raster remains Canvas even when deep zoom detail uses flutter_gpu.

For spatial tile/LoD boundaries, also enable pdfDebugPaintDetailBounds.value. Hosts can build their own diagnostics panel from PdfTileRasterDiagnostics.instance, which is a Listenable and exposes typed page(...) / forNamespace(...) snapshots as well as the existing JSON-safe support export.

Pages with other transparency groups or soft masks, non-nested radial gradients, gradient overprint, unsafe overprint, complex clips around a non-zero soft-mask backdrop, unresolved substituted text, or missing image pixels are rejected as a whole rather than approximated. allowOverprintApproximation exists only for controlled experiments and defaults to false.

Persistent LoD tiles #

dart_pdf_editor can also persist accepted or Canvas-fallback tiles through the existing platform-neutral cache seam. Use a separate byte budget so tiles cannot evict cold-navigation previews:

final store = createPersistentCacheStore();
final rasterCache = PdfRasterCache(
  PdfDiskCache(store, namespace: 'page-previews'),
  tiles: PdfDiskCache(
    store,
    namespace: 'lod-tiles',
    maxBytes: 256 << 20,
  ),
);

PdfReader(
  bytes: bytes,
  rasterCache: rasterCache,
  tileRasterBackend: gpuBackend,
)

A disk lookup races the ordinary tile render, so a cold or slow store cannot delay first content. A hit may win the race and populate the memory LRU; a fresh tile is admitted to the compositor before its PNG encoding and disk write begin. Cache keys include the document, revision/page stamps, page visual options, tile coordinate, exact region, dimensions, and LoD ratio.

Validation and measurement #

Run native tests with the opt-in flags:

fvm flutter test --enable-impeller --enable-flutter-gpu

test/gpu_corpus_test.dart checks every Ghent file and the PDF.js corpus, comparing every GPU-accepted page against Canvas while treating conservative rejection as the intended fallback. test/real_document_benchmark_test.dart measures scene recording/image decode, cold and warm 512 px LoDs, forced visual settle, Canvas parity, upload/cache counters, and RSS. It is opt-in via PDF_GPU_BENCHMARK_PDF and accepts a zero-based comma-separated page list in PDF_GPU_BENCHMARK_PAGES. Set PDF_GPU_BENCHMARK_OUT to a directory to save the center 512 px GPU and Canvas tiles for visual comparison, or PDF_GPU_BENCHMARK_MSAA=0 to isolate multisample antialiasing differences. Set PDF_GPU_BENCHMARK_WARMUP=1 to measure the viewer's pipeline warm-up before the first real tile. Set PDF_GPU_BENCHMARK_SCENE_WARMUP=1 to additionally compile and submit the retained scene before measuring that tile. Set PDF_GPU_BENCHMARK_SCENARIO to emit normalized PdfPerfLog scenario markers for each pipeline/scene/tile phase. CI uses those markers to run the checked-in tiling-pattern, radial-shading, hairline, advanced-blend, vector-mask transfer, PDF.js knockout soft-mask and isolated-knockout overlap, GWG168/169 vector soft-mask, and GWG1610/1611 text soft-mask pages plus the PDF.js Latin and CJK system-font outline pages and deterministic deferred-mask fixture six times on macOS Metal. A generated repeated-advanced-blend page adds twelve ordered destination-sampling passes, giving blend optimizations a higher-signal measurement than the small PDF.js conformance page alone. CI compares the exact PR base and candidate on the same runner with balanced execution order, and publishes both a concise PR headline and a collapsed detailed trace. Set PDF_GPU_BENCHMARK_FIXTURE=deferred-mask instead of a PDF path to exercise a deterministic 1024x768 JPEG under a Flate grayscale soft mask. This fixture emits the same first-tile and Canvas scenarios whether the backend accepts the scene or production falls back to Canvas, so a backend-routing change remains a like-for-like comparison. Pipeline and scene warm-up scenario markers are suppressed for this production-route fixture; the warm-up itself still runs. An unmeasured Canvas pass immediately before the Canvas control keeps that reference warm on both the accepted and fallback routes without warming the cold first-tile measurement. Set PDF_GPU_BENCHMARK_ROUTE_CHANGE=1 for the same normalization when a PDF path, rather than the built-in fixture, changes from Canvas fallback to direct GPU. Route-change scene warm-up still runs when requested, matching the viewer's idle-prepared path, but its unmatched timing marker is omitted because the Canvas base has no GPU session to warm. CI uses it for the checked-in vector-mask transfer, knockout soft-mask, isolated-knockout overlap, hairline, advanced-blend, and GWG vector/text soft-mask pages. Set PDF_GPU_BENCHMARK_SYSTEM_TEXT=1 to enable the native system-font outline adapter. For a like-for-like macOS parity control, PDF_GPU_BENCHMARK_REGISTER_SYSTEM_FONTS=1 registers those same font bytes under Canvas's substitution-family names; CI combines both settings for the system-font and advanced-blend route-change scenarios. The corpus equivalents are GPU_CORPUS_SYSTEM_TEXT=1 and GPU_CORPUS_REGISTER_SYSTEM_FONTS=1; the designated macOS GPU lane runs that full parity matrix on every change. Set PDF_GPU_BENCHMARK_ANALYTIC_TEXT=0 or GPU_CORPUS_ANALYTIC_TEXT=0 for a same-build comparison against the retained stencil-fan text path. The exact overprint rerecord keeps its 512-cell default but skips scenes above 768 retained commands so a failed GPU retry cannot add seconds before Canvas fallback. Set GPU_CORPUS_OVERPRINT_RETRY_MAX_COMMANDS to override that corpus guard; FlutterGpuTileRasterBackend.overprintRetryMaxCommands is the runtime equivalent, and null removes the guard for exhaustive diagnostics. Set PDF_GPU_BENCHMARK_OVERPRINT=0 to exercise the production-default exact fallback policy; the benchmark otherwise enables its documented source-over approximation so more of a corpus can be measured on the GPU.

0
likes
130
points
661
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Opt-in Impeller flutter_gpu tile backend for dart_pdf_editor.

Repository (GitHub)
View/report issues
Contributing

Topics

#pdf #pdf-rendering #flutter #gpu #impeller

License

Apache-2.0 (license)

Dependencies

dart_pdf_editor, flutter, flutter_gpu, pdf_cos, pdf_document, pdf_graphics, vector_math

More

Packages that depend on dart_pdf_editor_flutter_gpu