Files
plotter/CMakeLists.txt
T
Martino Ferrari a08b184481 fix(plot,render,gl): live streaming measurements, handed-back GL state
Final whole-branch review fixes.

1 (Important). Plot::measurement() cached on (series pointer, sample count)
only, so a saturated StreamSeries -- whose size() stays pinned while x0()
advances every acquire() -- froze its readout on the first saturated frame
until a cursor moved. The cache key now carries the series' x0() (new
MeasureSlot::last_x0), so a sliding window recomputes and a static series
still does not: asserted with a new macro-gated counter hook
(plot/detail/measure_count.hpp, PLOT_COUNT_MEASURE_RECOMPUTES, wired like the
pyramid and ring counters, absent from the release library). New case
saturated_stream_measurement_tracks_the_sliding_window: saturated ring,
stationary cursors, ramp in absolute x -- zero recomputes for repeated reads
with nothing moving, one per acquire while it slides.

2-3, 9. Spec: 4.3 described a GL_LINE_STRIP path that does not exist (there is
one rasteriser -- instanced anti-aliased quads -- and dense_threshold_spp
selects the gather level, not a primitive); 8 named a PLOT_GL_CHECK() macro
that exists nowhere, and stated no exception policy. The table, the field's
comment and 8 now describe the real gather-level threshold, the real
glGetError() drain plus the stores' drop-to-orphan fallbacks, and the real
policy: library failures return Status/bool, allocating entry points propagate
std::bad_alloc.

4. The frame handed back neither GL_UNPACK_ALIGNMENT (init() sets 1 for the
atlas) nor the clear colour (queried once with a new glGetFloatv entry).
end_frame() restores both and renderer.hpp lists them in the residue
contract.

5. draw() kept a host TextBackend for layout while drawing with its own font.
The renderer now installs its font unconditionally -- strips and labels measure
and draw with one backend -- with a debug-build diagnostic if a different one
was set, and Plot::set_text_backend() is documented as the no-renderer path.
New case renderer_measures_the_labels_it_draws asserts layout, measuring and
the drawn label's ink agree.

6-10. draw()'s style overwrite stated at the call site; the load-sensitive
wall-clock bound in test_measure.cpp replaced by a counter assertion (same
interval, same fold count over a 512x length ratio); dump_frame_ppm drops
noexcept and says why it may allocate; error.txt ignored (not this project's,
not generated here) rather than deleted.

Verified: debug and release build clean (-Wall -Wextra -Wpedantic -Wconversion
-Wshadow), 4/4 ctest entries pass, 130 cases pass (was 127), umbrella and
header self-containment checks pass, and no counter symbol in the release
archive.
2026-09-28 03:42:34 +02:00

141 lines
5.3 KiB
CMake

cmake_minimum_required(VERSION 3.24)
project(plot VERSION 0.1.0 LANGUAGES CXX)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(plot_options)
add_library(plot_core
src/core/status.cpp
src/math/transform.cpp
src/data/minmax_pyramid.cpp
src/data/series.cpp
src/data/array_series.cpp
src/data/stream_series.cpp
src/gl/loader.cpp
src/gl/handle.cpp
src/render/shaders.cpp
src/render/bitmap_font.cpp
src/render/primitives.cpp
src/render/buffer.cpp
src/render/vertex_array.cpp
src/render/program.cpp
src/render/renderer.cpp
src/plot/ticks.cpp
src/plot/axis.cpp
src/plot/grid.cpp
src/plot/measure.cpp
src/plot/plot.cpp
src/input/controller.cpp
)
add_library(plot::core ALIAS plot_core)
# What the target is called in an installed tree. Without this the export set is
# namespaced by the target's own name (plot::plot_core), while the build tree --
# and every consumer, and this project's own comments -- says plot::core.
set_target_properties(plot_core PROPERTIES EXPORT_NAME core)
target_include_directories(plot_core PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_features(plot_core PUBLIC cxx_std_20)
target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})
plot_apply_warnings(plot_core)
# Adapters own the surface and translate their host's events; examples link the
# adapter they demo. Both are opt-in, so the core library and its tests still build
# on a bare toolchain.
#
# They are added before the test suite on purpose: a test that drives an adapter
# needs that adapter's targets to exist, and with both switches on the suite can
# then exercise them (see tests/CMakeLists.txt).
if(PLOT_BUILD_GLFW)
add_subdirectory(adapters/glfw)
endif()
if(PLOT_BUILD_IMGUI)
add_subdirectory(adapters/imgui)
endif()
if(PLOT_BUILD_QT6)
add_subdirectory(adapters/qt6)
endif()
if(PLOT_BUILD_GTK4)
add_subdirectory(adapters/gtk4)
endif()
if(PLOT_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if(PLOT_BUILD_TESTS)
# Test-only: counts the pyramid's covering-decomposition entries and its bucket
# merges so a test can pin the query's O(log n) cost and the append path's
# amortised O(1) cost, neither of which any answer reveals. Off in every
# non-test build (see include/plot/data/detail/{cover,merge}_count.hpp).
#
# PLOT_RING_STATS is the same for the persistent-mapped instance ring: the
# properties a frame must hold there -- no slot written twice within a frame,
# and one fence wait at most, at the start of the frame -- are invisible in the
# pixels, so a test reads counters instead (see
# include/plot/render/detail/ring_stats.hpp).
#
# PLOT_COUNT_MEASURE_RECOMPUTES is the same for the measurement cache: whether
# `Plot::measurement()` recomputed or returned a cached result is invisible in
# the value it returns, so a test that has to tell a live cache from one frozen
# on a stale window counts re-evaluations (see
# include/plot/plot/detail/measure_count.hpp).
target_compile_definitions(plot_core PRIVATE PLOT_PYRAMID_COUNT_COVERS
PLOT_PYRAMID_COUNT_MERGES
PLOT_RING_STATS
PLOT_COUNT_MEASURE_RECOMPUTES)
enable_testing()
add_subdirectory(tests)
endif()
# Install and export. The core always goes in; each adapter joins the set only if
# its toolkit can be re-found on a consumer's machine, which the adapter decided
# when it was added (plot_record_adapter_export() -- the decisions and the reasons
# are in the configure log above). plotConfig.cmake then re-finds exactly those
# toolkits before including the targets file the adapters are described in, so
# find_package(plot) + target_link_libraries(consumer PRIVATE plot::core) works
# from an installed prefix with no -I or -L of the consumer's own.
if(PLOT_INSTALL)
# The adapters that could be promised to a consumer, in the order they were
# added, and the find_dependency() calls that re-create their toolkits there.
get_property(PLOT_ADAPTER_EXPORT_TARGETS GLOBAL PROPERTY PLOT_ADAPTER_EXPORT_TARGETS)
# Substituted into plotConfig.cmake below (@PLOT_ADAPTER_DEPENDENCIES@).
get_property(PLOT_ADAPTER_DEPENDENCIES GLOBAL PROPERTY PLOT_ADAPTER_DEPENDENCIES)
include(CMakePackageConfigHelpers)
# No INCLUDES DESTINATION here: every target already declares
# $<INSTALL_INTERFACE:include> in its own target_include_directories(), and
# asking for it twice puts the same directory in the export twice.
install(TARGETS plot_core ${PLOT_ADAPTER_EXPORT_TARGETS}
EXPORT plotTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY include/plot DESTINATION include)
install(EXPORT plotTargets
NAMESPACE plot::
FILE plotTargets.cmake
DESTINATION lib/cmake/plot)
configure_package_config_file(
cmake/plotConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/plotConfig.cmake"
INSTALL_DESTINATION lib/cmake/plot)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/plotConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/plotConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/plotConfigVersion.cmake"
DESTINATION lib/cmake/plot)
endif()