feat(plot)!: harden pipeline, XY series, dash/markers, touch, packaging
Breaking: Series::gather emits x relative to x0() and takes a GatherMode; consumers reconstruct absolute x in f64 before mapping (series.hpp documents the contract). Bugs, each pinned by a fail-before regression test: - render: f32 staging quantised epoch-time x into 128 px columns; every data-space draw now stages to_space(x) - to_space(origin) with the origin folded into the offset uniform in f64 (epoch/zero-origin twins rasterise identically) - ticks: major test was ULP-fragile and blanked epoch axes; majors are an integer-index test and are never truncated by the minor cap - gather: LOD envelope emitted buckets straddling [begin,end); ceil/floor bucket edges + raw lead/trail spans - live follow: log x axes held a linear span and zoomed every frame; span and offset now live in axis space (plot + both figure link-group paths) - input: a zero-notch wheel disarmed auto_scale on every axis; dy==0 pans x, factor == 1 / inf refused in wheel and drag zoom - render: the single text flush painted legend names over the crosshair readout panel; two batches, under flushed before the panel - data: NaN dropped from min/max unless it seeded the fold; non-finite samples are now excluded from every aggregate and from cached bounds Robustness: viewport normalisation (no clamp UB), non-finite range refusal, pyramid level cap at 57 (no shift/div UB through public gather), rounded ring capacity documented + pre-sized, GL blend func and UNPACK_ALIGNMENT save/restore, Renderer::release_gl() teardown, u64 buffer epochs, annotation captions clamped, button-aware GLFW row capture, cached figure row rects (no per-event layout), ruler-pair measurement refused, span-nonfinite guards in axis_ends. Features: XY series (sorted x, binary-search neighbours/snap, LOD over sample index); StreamSeries::freeze(); ArraySeries::push(); per-trace dash and markers (scatter at width 0) with auto or literal stride; GatherMode Envelope/Nearest/Mean via bucket_sum/sumsq; Style::line_aa_px; per-glyph text colour (readout values in trace colour); wheel-dx x pan; arrow-key pan; Escape cancels a drag and restores captured ranges; modifier-masked box zoom (Shift both, Ctrl x, Alt y); double-click reset via PointerEvent::time_s; ImGui key repeat unified; pinch gestures on Qt and GTK; pixelDelta wheel path and DevicePixelRatioChange handling; GNUInstallDirs, SOVERSION, SameMinorVersion, generated plot/version.hpp, relocatable plot.pc, licence install, FILES_MATCHING, GitHub Actions CI (debug/coverage/asan). Suite 283 -> 350 cases, 0 failed; coverage 93.9% (3730/3971) over 24 files, floor 80/90 held; ASAN preset suite green; all four demos run headless (the GTK scripted-resize check needs a window manager and fails identically on the parent commit); readme_main.cpp committed and compiles with the README command; pkg-config and find_package consumption verified from a relocated install prefix.
This commit is contained in:
1 parent
f132a5ec92
commit
38ef459897
77 files changed
+5905
-688
No files matched your search
+17
-9
@@ -19,6 +19,7 @@ uniform vec2 u_scale; // data -> pixels
|
||||
uniform vec2 u_offset; // pixels
|
||||
uniform vec2 u_viewport; // render target size, pixels
|
||||
uniform float u_width; // stroke width, pixels
|
||||
uniform float u_aa; // AA fringe half-width, pixels
|
||||
|
||||
out float v_dist; // signed distance to the segment axis, pixels
|
||||
// Distance travelled from a_p0, pixels -- and the dash's phase anchor. Both of the
|
||||
@@ -33,7 +34,7 @@ void main() {
|
||||
vec2 p1 = a_p1 * u_scale + u_offset;
|
||||
vec2 d = p1 - p0;
|
||||
float len = length(d);
|
||||
float half_w = u_width * 0.5 + 1.0; // + one pixel of AA fringe
|
||||
float half_w = u_width * 0.5 + u_aa; // + the AA fringe
|
||||
// A degenerate segment has no direction: pin the normal, and give the quad
|
||||
// area by stretching its two ends apart along x. Collapsing all four corners
|
||||
// onto p0 would rasterise nothing, and a flat trace staged as p == p (the
|
||||
@@ -64,6 +65,7 @@ in float v_dist; // signed distance to the segment axis, pixels
|
||||
in float v_along; // distance travelled from a_p0, pixels
|
||||
|
||||
uniform float u_width; // stroke width, pixels
|
||||
uniform float u_aa; // AA fringe half-width, pixels
|
||||
uniform vec4 u_color;
|
||||
uniform vec2 u_dash; // dash pattern in pixels: x = ink, y = gap; y <= 0 is solid
|
||||
|
||||
@@ -85,12 +87,15 @@ void main() {
|
||||
}
|
||||
float half_w = u_width * 0.5;
|
||||
float d = abs(v_dist);
|
||||
if (d > half_w + 1.0) {
|
||||
if (d > half_w + u_aa) {
|
||||
discard; // outside the fringe; nothing to blend
|
||||
}
|
||||
// Ramp over the last pixel of the half width so a thin stroke keeps its
|
||||
// weight instead of aliasing into a dashed line.
|
||||
float alpha = 1.0 - smoothstep(half_w - 0.5, half_w + 0.5, d);
|
||||
// Ramp over the fringe so a thin stroke keeps its weight instead of aliasing
|
||||
// into a dashed line: the ramp is centred on `half_w` and spans `u_aa`, so
|
||||
// `u_aa == 1.0` -- the style default -- reproduces the historical single-pixel
|
||||
// ramp exactly. `u_aa` is the style's `line_aa_px` in device pixels, clamped
|
||||
// at 0.25 by the renderer.
|
||||
float alpha = 1.0 - smoothstep(half_w - 0.5 * u_aa, half_w + 0.5 * u_aa, d);
|
||||
o_color = vec4(u_color.rgb, u_color.a * alpha);
|
||||
}
|
||||
)";
|
||||
@@ -99,12 +104,14 @@ const char* const kTextVS = R"(#version 330 core
|
||||
layout(location = 0) in vec2 a_pos; // per-instance glyph cell top-left, pixels
|
||||
layout(location = 1) in vec2 a_uv; // per-instance glyph rect top-left, normalised
|
||||
layout(location = 2) in vec2 a_axis; // per-instance cell +x axis, unit, pixels
|
||||
layout(location = 3) in vec4 a_colour; // per-instance tint, straight alpha
|
||||
|
||||
uniform vec2 u_viewport; // render target size, pixels
|
||||
uniform vec2 u_px_range; // glyph cell on screen, pixels (width, height)
|
||||
uniform vec2 u_glyph_uv; // glyph cell in the atlas, normalised (width, height)
|
||||
|
||||
out vec2 v_uv; // normalised atlas coordinate of this fragment
|
||||
out vec2 v_uv; // normalised atlas coordinate of this fragment
|
||||
out vec4 v_colour; // the instance's tint
|
||||
|
||||
void main() {
|
||||
// The cell's two axes: the run direction the instance carries and its
|
||||
@@ -123,15 +130,16 @@ void main() {
|
||||
// The same handedness as the cell, so the glyph's rect lands on it the right
|
||||
// way round: u runs along the basis, v down the glyph.
|
||||
v_uv = a_uv + corner * u_glyph_uv;
|
||||
v_colour = a_colour;
|
||||
}
|
||||
)";
|
||||
|
||||
const char* const kTextFS = R"(#version 330 core
|
||||
in vec2 v_uv; // normalised atlas coordinate of this fragment
|
||||
in vec2 v_uv; // normalised atlas coordinate of this fragment
|
||||
in vec4 v_colour; // the instance's tint
|
||||
|
||||
uniform sampler2D u_atlas; // GL_R8 glyph atlas, GL_NEAREST
|
||||
uniform vec2 u_tex_size; // atlas size in texels
|
||||
uniform vec4 u_color;
|
||||
|
||||
out vec4 o_color;
|
||||
|
||||
@@ -141,7 +149,7 @@ void main() {
|
||||
// glyph cell; rounding to a centre removes the tie by construction.
|
||||
vec2 texel = clamp(floor(v_uv * u_tex_size), vec2(0.0), u_tex_size - vec2(1.0));
|
||||
float coverage = texture(u_atlas, (texel + vec2(0.5)) / u_tex_size).r;
|
||||
o_color = vec4(u_color.rgb, u_color.a * coverage);
|
||||
o_color = vec4(v_colour.rgb, v_colour.a * coverage);
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
Reference in new issue
Block a user