Skip to content

perf(PLY): fast binary-LE reader via vendored miniply (5.5×, int32 cells)#75

Open
akaszynski wants to merge 3 commits into
mainfrom
feat/fast-ply-reader
Open

perf(PLY): fast binary-LE reader via vendored miniply (5.5×, int32 cells)#75
akaszynski wants to merge 3 commits into
mainfrom
feat/fast-ply-reader

Conversation

@akaszynski

Copy link
Copy Markdown
Member

Summary

Swaps vtkPLYReader's internals for a fast columnar path on the common case (binary little-endian PLY read from a file), keeping the outward API constant and the output byte-identical to the legacy reader (width-relaxed: int32 cell storage, see below). Falls back to the legacy reader for anything outside the fast envelope.

  • 5.5× faster: 23.2 ms vs 126.5 ms (stock VTK 9.6.2) on a 360k-vertex / 717k-triangle mesh.
  • Vendors miniply (MIT, Vilya Harvey) for bulk column extraction instead of vtkPLY's per-row ply_get_element dispatch.

Correctness

The fast path engages only when every consumed property's stored type equals its VTK destination type (float x/y/z, normals, tcoords; uchar colors/intensity; integer index lists), so each value is a verbatim little-endian copy → byte-identical by construction. Point and face order are preserved (PLY faces index the point array), and polygons are not triangulated (miniply list path reproduces the exact offsets/connectivity).

Declines to the legacy reader for: ASCII / big-endian (last-ULP parse risk), the per-face texcoord point-duplication path, non-matching stored types, list-valued vertex elements, face-before-vertex ordering, missing vertex_indices. Comments are re-scanned from the header so GetComments() stays faithful.

Width-relaxed cell storage

Cell offsets/connectivity are stored as int32 when values fit (robust check: bounded by numPts for connectivity and total for offsets), per the fvtk int32-default rule — integer values identical to stock, container narrower (halves cell-array memory).

Validation

18-file matrix (all property combos + decline cases), stock VTK 9.6.2 vs fvtk, in the manylinux_2_28 container: 18/18 byte-exact PASS (mesh + comments), fast path engaged on 14/14 binary-LE files, correctly declined 4, int32 cell storage confirmed.

🤖 Generated with Claude Code

akaszynski and others added 3 commits June 19, 2026 09:41
… swap)

Swap vtkPLYReader's internals for a fast columnar path on the common case
(binary little-endian PLY read from a file), keeping the outward API constant
and the output byte-identical to the legacy reader.

The legacy reader drives vtkPLY's per-row `ply_get_element` dispatch. The new
`ReadPLYFast` uses the vendored miniply parser (MIT, Vilya Harvey) to bulk-
extract whole property columns in one pass, then assembles the same vtkPolyData.

Byte-exactness is guaranteed by construction: the fast path engages ONLY when
every consumed property's stored type already equals its VTK destination type
(float x/y/z, normals, tcoords; uchar colors/intensity; integer index lists),
so each value is a verbatim little-endian copy. Point and face order are
preserved (PLY faces index the point array, so order is load-bearing) and
polygons are kept untriangulated. The miniply list path reproduces the exact
offsets/connectivity vtkCellArray would build.

Anything outside that narrow envelope declines (-1) and falls back to the
legacy reader untouched: ASCII / big-endian (last-ULP parse risk), the per-face
texcoord point-duplication path, non-matching stored types, list-valued vertex
elements, face-before-vertex ordering, or a missing vertex_indices list.
Comments are re-scanned from the header so GetComments() stays faithful.

miniply.cxx is pulled out of source-unity batching (vendored third-party).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply the fvtk-wide width-relaxed rule to the fast PLY reader: build the cell
array's offsets/connectivity as int32 by default, widening to int64 only when a
value cannot fit in int32 (numPolys/total >= 2^31). Integer VALUES are identical
to stock VTK; only the storage container narrows (stock defaults to int64),
halving the cell-array footprint for the common case. Byte-exactness is now
width-relaxed for integer arrays: values sacred, container width negotiable.

Also add an env-gated (FVTK_PLY_FASTPATH_TRACE) stderr breadcrumb so validation
can confirm which files engage the fast path; off by default, no API change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The int32-vs-int64 cell-array decision must guarantee EVERY stored value fits.
Connectivity values are point indices in [0, numPts); offset values are in
[0, total]. The previous check bounded numPolys, which does not bound the
connectivity values -- a mesh with few faces but >2^31 points could overflow
int32 connectivity. Bound numPts (covers connectivity) and total (covers
offsets); numPolys <= total so it needs no separate check. Two unsigned
compares, no per-element scan -- cheap and robust.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant