Commit Graph
15 Commits
Author SHA1 Message Date
Martino Ferrari 3555ab63d7 feat(render): Renderer with LOD selection, 3.3 and 4.3+ upload paths, headless smoke test 2026-09-27 23:03:08 +02:00
Martino Ferrari e0cdd95a3b fix(input): pan and box zoom through layout().axes, not the viewport
Every pixel-to-data conversion in the controller now maps through the
same rectangle Plot::x_transform/y_transform are built from:

- pan() derives the scale from layout().axes.w/.h, so a drag moves the
  grabbed data point by exactly the drag distance (the viewport model
  stranded it behind the pointer by the label-strip fraction).
- apply_box_zoom() clamps the selection to layout().axes and sets each
  range from the transforms (x_transform().to_data_x(l/r), y_transform()
  .to_data_y(bottom/top)); the selection's bottom edge is the y minimum.
- end_drag() takes the release event and refreshes the selection from its
  coordinates before applying, so a host that reports only press and
  release still frames the box instead of degrading it to a click.
- axis_zoom() uses a per-pixel sensitivity (200 px == one decade) rather
  than reusing the wheel's per-notch constant, so a pixel drag is no
  longer treated as a wheel notch.

Tests now assert the grab-and-drag invariant through the transform (the
old pixel-fraction assertions certified the viewport defect), add a
press+release-only box zoom, and pin the axis-zoom magnitude.
2026-09-27 22:41:42 +02:00
Martino Ferrari 5ab854104a feat(input): host-agnostic events and pan/zoom/cursor controller 2026-09-27 22:34:16 +02:00
Martino Ferrari 606e40a9f1 fix(plot): single-rounding clock labels, log-axis fits that refuse non-positive data 2026-09-27 22:28:56 +02:00
Martino Ferrari 93fc06f050 feat(plot): nice-number ticks, axes, grid, traces, cursors, measurement queries 2026-09-27 22:21:22 +02:00
Martino Ferrari b4e88fe0c1 fix(render): give degenerate segments area, pin font bit order, clarify vertex_count 2026-09-27 22:10:53 +02:00
Martino Ferrari 7cbc1c9955 feat(render): style, GLSL line/text pipelines, staging batches, bitmap font 2026-09-27 22:03:56 +02:00
Martino Ferrari 9a086b7d69 test(data): make the push/read race test deterministic
The completion check was scheduling-dependent and the test could lose its TSan

overlap window. The producer now signals done with a release store, the consumer

reads in a do/while keyed on that flag so at least one read is guaranteed by

construction, and a started/reading handshake makes the producer's remaining

pushes provably overlap the reader's loop. Bounded to 4096 pushes on the 256-slot

ring, and the assertions moved after join() so a failure reports instead of

terminating. With the pre-fix ring restored, four TSan runs still flag push vs

copy_from, so the case keeps its teeth.
2026-09-27 21:51:26 +02:00
Martino Ferrari 5f1a4991e2 fix(data): remove MinMaxPyramid::reduce, repoint its two test callers
The pyramid's public query surface is the bucket-grained trio
(reduce_buckets/sum_buckets/sumsq_buckets); reduce() was the one
non-*_buckets query and silently over-covered non-aligned ranges, which
is exactly the misuse the exactness split exists to prevent. Exact range
queries stay on Series.

- delete the declaration, its doc paragraph and the definition; no
  helper became dead (fold_range/cover_buckets/ceil_to_bucket are all
  still used by the *_buckets queries)
- pyramid_clear_and_rebuild: complete bucket 0 and assert its extrema via
  reduce_buckets(0, kBucket) -- a pyramid has no exact sub-bucket query
- stream_series_acquire_publishes_snapshot: s.pyramid().reduce(0, 100)
  -> s.reduce(0, 100), keeping the == 99.0 assertion
2026-09-27 21:45:03 +02:00
Martino Ferrari d9909f2115 fix(data): level-per-emission pyramid cover, relaxed-atomic ring slots
cover_buckets chose one level per pass, so an odd first whole bucket (half of all

measurement ranges) never reached a level above 0 and folded every remaining

bucket at level 0 -- 15624 entries for a 1e6-sample pyramid. Pick the largest

aligned, in-range, materialised level at each cursor instead; the new cost test

counts folded entries through a PLOT_PYRAMID_COUNT_COVERS-only hook (absent from

normal builds) and fails on the old body.

Ring slots are now relaxed std::atomic: overwrite-on-full means the producer

reuses the slot of a snapshot's oldest sample while the consumer reads it, which

was a data race, not just a stale value. copy_from loads explicitly instead of

std::copy; push stays lock-free. A new producer/consumer case is the one TSan

flags without this.

Also extends the wrapped-window test with x0/bounds/pyramid/sum/sumsq/gather

checks, and corrects the StreamSeries threading note to the sample-count bound.
2026-09-27 21:42:53 +02:00
Martino Ferrari 138fa478c3 feat(data): SPSC ring buffer, incremental min/max LOD pyramid, series types 2026-09-27 21:29:59 +02:00
Martino Ferrari 6866457155 fix(gl): gate fallible constructors on a complete loader table
- loader: add available(), true only after load() has resolved every
  required entry point (false at the start of load()). Program::from_source
  and the other fallible constructors return ErrorCode::Unsupported while it
  is false, so a failed load() with a partially populated table can no longer
  fault on an unresolved entry point.
- caps: GL_CONTEXT_PROFILE_MASK only exists from 3.2, so query it only on
  3.2+ contexts. A 3.0/3.1 query latches GL_INVALID_ENUM in the context, which
  the next unrelated glGetError() (e.g. read_pixel) then reports as failure.
- tests: gate the dlsym/libGL.so.1 fallback assertions on libGL being
  resolvable, so the GL suite also passes on a GLES-only host.
2026-09-27 21:15:57 +02:00
Martino Ferrari 258caa38a7 feat(gl): dependency-free GL loader, RAII handles, caps probe, headless EGL harness 2026-09-27 21:07:36 +02:00
Martino Ferrari ed71719df7 fix(math): keep from_range invertible for collapsed pixel spans
A zero pixel span previously produced sx/sy == 0, so to_data_* returned
NaN or +/-inf. axis_scale now substitutes a unit pixel span (1.0) when
|p1 - p0| <= 1e-12, keeping the mapping finite and invertible while
leaving non-collapsed spans byte-identical.

Add committed coverage for from_range (linear, flipped pixel axis, log x,
degenerate data range, collapsed pixel span) and log10_positive clamping.
2026-09-27 20:58:37 +02:00
Martino Ferrari 8a7e9e97f8 feat: project skeleton, core types and math with transform tests 2026-09-27 20:54:19 +02:00