Skip to content

build: macOS (arm64) support, CI lane, and PCL loader validation - #34

Merged
rxdu merged 4 commits into
mainfrom
build/macos-support
Sep 8, 2026
Merged

rxdu merged 4 commits into
mainfrom
build/macos-support

Conversation

@rxdu

@rxdu rxdu commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Makes quickviz build and test on macOS, and fixes several latent issues found along the way. Most are not macOS-specific — Linux masked them.

Portability fixes

  • object_feedback_handler.hpp included <GL/gl.h> unguarded, unlike mesh.cpp and opengl_capability_checker.cpp which already select glad. macOS ships OpenGL as a framework and has no GL/ directory, so this was a hard compile error. Guarded the same way, and taught the existing non-glad branches to use <OpenGL/...> on Apple so VIEWER_WITH_GLAD=OFF works there too.
  • glad's vendored khrplatform.h sat in include/glad/, so <KHR/khrplatform.h> resolved on Linux only because Mesa happens to install /usr/include/KHR/khrplatform.h — an undeclared dependency on a system package. Moved to include/KHR/, glad's own standard layout, so the vendored copy is used everywhere. This makes the Linux build depend on less.
  • glm was never referenced by CMake at all; it resolved implicitly because Ubuntu's libglm-dev installs into /usr/include. Added find_package(glm) and GLM_ENABLE_EXPERIMENTAL, which glm >= 1.0 requires for the GTX extensions the renderables use.

Build plumbing (both latent, neither macOS-specific)

  • gtest_discover_tests enumerates cases by running each test binary at build time, with a 5 s timeout. These binaries link GL, cairo and optionally OpenCV/PCL, and where those ship as many small shared libraries the dynamic loader alone exceeds that. Discovery then times out and CMake deletes the executable, surfacing much later as a baffling <target>_NOT_BUILT ctest failure. Switched to PRE_TEST discovery.
  • OpenCV and PCL export their whole dependency prefix onto the include path. Where a system googletest exists there, its headers were found ahead of the vendored copy while the vendored libgtest.a still linked — an ABI mismatch appearing only as undefined testing::internal symbols. The vendored include dirs are now prepended.

PCL loader: validate the header, not just the return code

LoadPCDInternal/LoadPLYInternal treated a load as successful whenever PCL did not return -1. PCL does not honour that: given garbage, 1.15 logs "number of points is zero", defaults HEIGHT, reports x/y/z unmatched, and returns 0. Non-point-cloud data loaded silently as an empty cloud.

The header is the reliable signal, and the check keys on it rather than the point count for a specific reason: a malformed file and a legitimately empty one both yield zero points, so rejecting on emptiness would break valid POINTS 0 clouds. Every PCD/PLY readable into a PCL point type declares x/y/z, and that stays true when the cloud is empty.

Applied to all four load paths. AnalyzeFields is deliberately untouched — it answers "what fields does this file have", and all-false is a legitimate answer.

Two tests: InvalidPLYFileError (the fixture built a garbage .ply that nothing asserted on) and EmptyButValidPCDLoads, which pins the distinction the fix rests on so it cannot later be "simplified" into a point-count check.

Ubuntu 22.04/24.04 ship PCL 1.12/1.14 and have not hit this yet. They will on the next PCL bump.

CI lane

macos-14 (arm64), AppleClang + libc++, BUILD_TESTING=ON. The GUI itself is not exercised — there is no automated suite for it and the samples need a display, so only compilation and the non-GUI tests are covered.

Verification

macOS 15 (arm64): 123/123 tests pass.

Known, pre-existing and untouched: the PCLLoaderTest fixture shares a temp directory, so it races under ctest -j (9 failures parallel vs 0 serial). Both this lane and the existing Ubuntu lanes run ctest serially, so it is latent on Linux too.

rxdu added 4 commits September 6, 2026 22:59
Three Linux assumptions kept quickviz from compiling on macOS. None are
fundamental -- the viewer's GL usage is already portable (it requests a
3.2 core + forward-compatible context on Apple, and the shaders are
#version 330, under the 4.1 cap macOS enforces).

- object_feedback_handler.hpp included <GL/gl.h> unguarded, unlike
  mesh.cpp and opengl_capability_checker.cpp which already select glad.
  macOS ships OpenGL as a framework and has no GL/ directory, so this was
  a hard compile error. Guarded the same way, and taught the existing
  non-glad branches to use <OpenGL/...> on Apple so VIEWER_WITH_GLAD=OFF
  works there too.

- glad.h includes <KHR/khrplatform.h>, but the vendored copy sat in
  include/glad/. On Linux this resolved only because Mesa happens to
  install /usr/include/KHR/khrplatform.h -- an undeclared dependency on a
  system package. Moved it to include/KHR/, glad's own standard layout,
  so the vendored copy is used on every platform.

- glm was never referenced by CMake at all; it resolved implicitly because
  Ubuntu's libglm-dev installs into /usr/include. Added find_package(glm)
  so other prefixes work, and defined GLM_ENABLE_EXPERIMENTAL, which
  glm >= 1.0 requires for the GTX extensions the renderables use.

Linux behaviour is unchanged; the KHR move makes it depend on less.

Verified: builds clean on macOS 15 (arm64) with no external shims and no
manual compile flags. The GUI itself is still unexercised -- quickviz has
no automated suite and the samples need a display.
The lane itself is AppleClang + libc++ on macos-14 with BUILD_TESTING=ON.
Two build-plumbing problems had to be fixed before any test could run;
neither is macOS-specific, both were latent.

- gtest_discover_tests enumerates cases by RUNNING each test binary at
  BUILD time, with a 5 s timeout. These binaries link GL, cairo and
  optionally OpenCV/PCL, and where those ship as many small shared
  libraries the dynamic loader alone exceeds that. Discovery then times
  out and CMake DELETES the executable, which surfaces much later as a
  baffling <target>_NOT_BUILT ctest failure. Switched to PRE_TEST
  discovery, which removes the build-time dependency on process startup.

- OpenCV and PCL export their whole dependency prefix onto the include
  path. Where a system googletest exists there, its headers were found
  ahead of the vendored copy while the vendored libgtest.a still linked --
  an ABI mismatch appearing only as undefined testing::internal symbols.
  The vendored include dirs are now prepended.

KNOWN FAILURE on this lane: PCLLoaderTest.InvalidFileError. PCL 1.15 (what
Homebrew ships) parses a garbage .pcd into a zero-point cloud and returns
success, so LoadPCDInternal's `== -1` check never fires and no
CorruptedFileException is thrown. That is a loader robustness gap, not a
platform issue, and it will reach the Ubuntu lanes when they move past
PCL 1.14. Left for a separate decision rather than changing point-cloud
loading semantics here.

Otherwise verified on macOS 15 (arm64): 119/121 pass, the second failure
being the aggregate target that reruns the first.
LoadPCDInternal/LoadPLYInternal treated a load as successful whenever PCL
did not return -1. PCL's readers do not reliably signal a malformed file
that way: given arbitrary non-point-cloud data, PCL 1.15 logs "number of
points is zero", defaults HEIGHT, reports that x/y/z were not matched, and
returns 0. Garbage therefore loaded as an empty cloud and
CorruptedFileException was never thrown.

The header is the reliable signal. Every PCD/PLY readable into a PCL point
type declares x, y and z, so their absence means the file is not a point
cloud. Crucially this stays true for a legitimately EMPTY cloud -- POINTS 0
with a full FIELDS line -- which must keep loading, so the check keys on the
header rather than on the point count.

Applied to all four load paths: the LoadToPCL templates and the
renderer-facing LoadPCD/LoadPLY, which already had the fields in hand.
AnalyzeFields is deliberately untouched: it answers "what fields does this
file have", and all-false is a legitimate answer to that question.

Tests: InvalidPLYFileError closes the gap where the fixture built a garbage
.ply that nothing asserted on, and EmptyButValidPCDLoads pins the
distinction the fix depends on -- a malformed file and a valid empty one
both yield zero points, so only the header separates them.

Not a macOS-specific fix. Ubuntu 22.04/24.04 ship PCL 1.12/1.14 and have not
hit it yet; they will on the next PCL bump.

Verified on macOS 15 (arm64): 123/123 tests pass, up from 121 with 2
failing.
- ThreadSafeQueueTest.PopTimeout asserted that a 50 ms wait returns within
  100 ms. That is a claim about how promptly the OS reschedules a blocked
  thread, not about the queue, and a loaded runner overshoots it -- CI
  measured 143 ms. The lower bound is the real contract and is unchanged;
  the upper bound now has a wide margin and guards only against waiting
  forever. This was latent on Linux too.

- Benchmarks are excluded from the macOS lane. They open a real GL window
  and the hosted macOS runner is headless. Unlike Linux, GLFW initialises
  fine there (Cocoa is always present) and fails only later at window
  creation ("NSGL: Failed to find a suitable pixel format"), so the process
  aborts rather than degrading. The correctness suites already skip GUI
  tests on their own. Linux keeps running benchmarks.

Verified on macOS 15 (arm64): 123/123 tests pass with -LE benchmark.
@rxdu
rxdu force-pushed the build/macos-support branch from 95f15b9 to 680c0b2 Compare September 6, 2026 15:00
@rxdu
rxdu merged commit 236d7c4 into main Sep 8, 2026
6 checks passed
@rxdu
rxdu deleted the build/macos-support branch September 8, 2026 01:02
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