Files
plotter/include/plot/gl/api.hpp
T
Martino Ferrari 38ef459897
CI / debug (tests + all adapters) (push) Canceled after 0s
CI / coverage (floor + measured table) (push) Canceled after 0s
CI / asan (AddressSanitizer) (push) Canceled after 0s
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.
2026-09-30 17:32:06 +02:00

220 lines
11 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#pragma once
#include <plot/core/types.hpp>
// The GL surface of this library is hand-written on purpose: the core target
// links and includes nothing third-party, so there is no glad/GLEW/GLM and no
// Khronos header. Everything below is either a typedef fixed by the OpenGL
// specification or an entry point resolved at runtime by `plot::gl::load()`
// (see loader.hpp). Only OpenGL 3.3 core is assumed; anything newer is guarded
// by the capability probe in caps.hpp.
namespace plot::gl {
using GLenum = unsigned int;
using GLuint = unsigned int;
using GLint = int;
using GLsizei = int;
using GLboolean = unsigned char;
using GLbitfield = unsigned int;
using GLubyte = unsigned char;
using GLchar = char;
using GLintptr = std::intptr_t;
using GLsizeiptr = std::ptrdiff_t;
using GLuint64 = u64;
using GLsync = void*; // opaque sync object (`struct __GLsync*`)
inline constexpr GLboolean GL_FALSE = 0;
inline constexpr GLboolean GL_TRUE = 1;
// Enumerant values are fixed by the OpenGL specification; comment each group
// with the version or extension that introduced it.
enum : GLenum {
// Errors and primitives.
GL_NO_ERROR = 0x0000,
GL_LINE_STRIP = 0x0003,
GL_TRIANGLES = 0x0004,
GL_TRIANGLE_STRIP = 0x0005,
// Clear state.
GL_COLOR_BUFFER_BIT = 0x00004000,
GL_COLOR_CLEAR_VALUE = 0x0C22,
// Data types (unsigned short is the index/16-bit data type).
GL_UNSIGNED_BYTE = 0x1401,
GL_UNSIGNED_SHORT = 0x1403,
GL_FLOAT = 0x1406,
// Strings and context queries.
GL_VENDOR = 0x1F00,
GL_RENDERER = 0x1F01,
GL_VERSION = 0x1F02,
GL_EXTENSIONS = 0x1F03,
GL_NUM_EXTENSIONS = 0x821D /* 3.0 */,
GL_CONTEXT_PROFILE_MASK = 0x9126 /* 3.2 */,
GL_CONTEXT_CORE_PROFILE_BIT = 0x00000001,
// Blending.
GL_BLEND = 0x0BE2,
GL_ZERO = 0x0000,
GL_ONE = 0x0001,
GL_SRC_ALPHA = 0x0302,
GL_ONE_MINUS_SRC_ALPHA = 0x0303,
GL_BLEND_SRC_RGB = 0x80C9 /* 1.4 */,
GL_BLEND_DST_RGB = 0x80C8 /* 1.4 */,
GL_BLEND_SRC_ALPHA = 0x80CB /* 1.4 */,
GL_BLEND_DST_ALPHA = 0x80CA /* 1.4 */,
// Pixel store state.
GL_UNPACK_ALIGNMENT = 0x0CF5,
GL_PACK_ALIGNMENT = 0x0D05,
// Shaders and programs.
GL_FRAGMENT_SHADER = 0x8B30,
GL_VERTEX_SHADER = 0x8B31,
GL_COMPILE_STATUS = 0x8B81,
GL_LINK_STATUS = 0x8B82,
// Buffers (3.3 baseline plus the 4.4 storage flags used by the caps-driven
// persistent-mapping path).
GL_ARRAY_BUFFER = 0x8892,
GL_DYNAMIC_DRAW = 0x88E8,
GL_STATIC_DRAW = 0x88E4,
GL_STREAM_DRAW = 0x88E0,
GL_MAP_WRITE_BIT = 0x0002 /* 4.4 */,
GL_MAP_PERSISTENT_BIT = 0x0040 /* 4.4 */,
GL_MAP_COHERENT_BIT = 0x0080 /* 4.4 */,
// Rasterisation state (1.0).
GL_SCISSOR_TEST = 0x0C11,
// Textures.
GL_TEXTURE_2D = 0x0DE1,
GL_TEXTURE0 = 0x84C0,
GL_TEXTURE_MAG_FILTER = 0x2800,
GL_TEXTURE_MIN_FILTER = 0x2801,
GL_TEXTURE_WRAP_S = 0x2802,
GL_TEXTURE_WRAP_T = 0x2803,
GL_NEAREST = 0x2600,
GL_LINEAR = 0x2601,
GL_CLAMP_TO_EDGE = 0x812F,
GL_RED = 0x1903,
GL_RGBA = 0x1908,
GL_R8 = 0x8229,
GL_RGBA8 = 0x8058,
// Framebuffer objects (renderbuffers back depth attachments).
GL_FRAMEBUFFER = 0x8D40,
GL_COLOR_ATTACHMENT0 = 0x8CE0,
GL_FRAMEBUFFER_COMPLETE = 0x8CD5,
GL_RENDERBUFFER = 0x8D41,
// Sync objects (3.2).
GL_SYNC_GPU_COMMANDS_COMPLETE = 0x9117,
GL_SYNC_FLUSH_COMMANDS_BIT = 0x00000001,
GL_ALREADY_SIGNALED = 0x911A,
GL_CONDITION_SATISFIED = 0x911C,
};
// Single source of truth for the runtime entry points: every entry is declared
// as a function pointer here and resolved by `plot::gl::load()`. Adding an
// entry makes it required, so only add what the library actually calls.
#define PLOT_GL_FUNCS(X) \
/* context and state */ \
X(const GLubyte*, glGetString, (GLenum)) \
X(const GLubyte*, glGetStringi, (GLenum, GLuint)) \
X(void, glGetIntegerv, (GLenum, GLint*)) \
X(void, glGetFloatv, (GLenum, float*)) \
X(GLenum, glGetError, (void)) \
X(void, glFinish, (void)) \
X(void, glViewport, (GLint, GLint, GLsizei, GLsizei)) \
X(void, glClearColor, (float, float, float, float)) \
X(void, glClear, (GLbitfield)) \
X(void, glEnable, (GLenum)) \
X(GLboolean, glIsEnabled, (GLenum)) \
X(void, glBlendFunc, (GLenum, GLenum)) \
X(void, glBlendFuncSeparate, (GLenum, GLenum, GLenum, GLenum)) /* 1.4 */ \
X(void, glPixelStorei, (GLenum, GLint)) \
X(void, glDisable, (GLenum)) \
X(void, glScissor, (GLint, GLint, GLsizei, GLsizei)) \
/* shaders and programs */ \
X(GLuint, glCreateShader, (GLenum)) \
X(void, glShaderSource, (GLuint, GLsizei, const GLchar* const*, const GLint*)) \
X(void, glCompileShader, (GLuint)) \
X(void, glGetShaderiv, (GLuint, GLenum, GLint*)) \
X(void, glGetShaderInfoLog, (GLuint, GLsizei, GLsizei*, GLchar*)) \
X(void, glDeleteShader, (GLuint)) \
X(GLuint, glCreateProgram, (void)) \
X(void, glAttachShader, (GLuint, GLuint)) \
X(void, glDetachShader, (GLuint, GLuint)) \
X(void, glLinkProgram, (GLuint)) \
X(void, glGetProgramiv, (GLuint, GLenum, GLint*)) \
X(void, glGetProgramInfoLog, (GLuint, GLsizei, GLsizei*, GLchar*)) \
X(void, glDeleteProgram, (GLuint)) \
X(void, glUseProgram, (GLuint)) \
X(GLint, glGetUniformLocation, (GLuint, const GLchar*)) \
X(void, glUniform1i, (GLint, GLint)) \
X(void, glUniform1f, (GLint, float)) \
X(void, glUniform2f, (GLint, float, float)) \
X(void, glUniform4f, (GLint, float, float, float, float)) \
X(void, glUniform1fv, (GLint, GLsizei, const float*)) \
X(void, glUniform2fv, (GLint, GLsizei, const float*)) \
/* buffers */ \
X(void, glGenBuffers, (GLsizei, GLuint*)) \
X(void, glDeleteBuffers, (GLsizei, const GLuint*)) \
X(void, glBindBuffer, (GLenum, GLuint)) \
X(void, glBufferData, (GLenum, GLsizeiptr, const void*, GLenum)) \
X(void, glBufferSubData, (GLenum, GLintptr, GLsizeiptr, const void*)) \
X(void*, glMapBufferRange, (GLenum, GLintptr, GLsizeiptr, GLbitfield)) \
X(GLboolean, glUnmapBuffer, (GLenum)) \
/* vertex arrays */ \
X(void, glGenVertexArrays, (GLsizei, GLuint*)) \
X(void, glDeleteVertexArrays, (GLsizei, const GLuint*)) \
X(void, glBindVertexArray, (GLuint)) \
X(void, glEnableVertexAttribArray, (GLuint)) \
X(void, glVertexAttribPointer, (GLuint, GLint, GLenum, GLboolean, GLsizei, const void*)) \
X(void, glVertexAttribDivisor, (GLuint, GLuint)) \
/* drawing */ \
X(void, glDrawArrays, (GLenum, GLint, GLsizei)) \
X(void, glDrawArraysInstanced, (GLenum, GLint, GLsizei, GLsizei)) \
/* textures */ \
X(void, glGenTextures, (GLsizei, GLuint*)) \
X(void, glDeleteTextures, (GLsizei, const GLuint*)) \
X(void, glActiveTexture, (GLenum)) \
X(void, glBindTexture, (GLenum, GLuint)) \
X(void, glTexImage2D, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*)) \
X(void, glTexParameteri, (GLenum, GLenum, GLint)) \
/* framebuffers */ \
X(void, glGenFramebuffers, (GLsizei, GLuint*)) \
X(void, glDeleteFramebuffers, (GLsizei, const GLuint*)) \
X(void, glBindFramebuffer, (GLenum, GLuint)) \
X(void, glFramebufferTexture2D, (GLenum, GLenum, GLenum, GLuint, GLint)) \
X(GLenum, glCheckFramebufferStatus, (GLenum)) \
X(void, glReadPixels, (GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*)) \
X(void, glGenRenderbuffers, (GLsizei, GLuint*)) \
X(void, glDeleteRenderbuffers, (GLsizei, const GLuint*)) \
X(void, glBindRenderbuffer, (GLenum, GLuint)) \
X(void, glRenderbufferStorage, (GLenum, GLenum, GLsizei, GLsizei)) \
X(void, glFramebufferRenderbuffer, (GLenum, GLenum, GLenum, GLuint)) \
/* sync objects */ \
X(GLsync, glFenceSync, (GLenum, GLbitfield)) \
X(GLenum, glClientWaitSync, (GLsync, GLbitfield, GLuint64)) \
X(void, glDeleteSync, (GLsync))
// Entry points above the 3.3 baseline, resolved best-effort: a driver that lacks
// one leaves it null and `load()` still succeeds, so the library keeps working on
// a 3.3-only context. Every one of these is reached only after `caps()` -- or an
// explicit null check -- says the context can do it (see caps.hpp).
#define PLOT_GL_OPTIONAL_FUNCS(X) \
/* persistent buffer mapping, GL 4.4 / ARB_buffer_storage */ \
X(void, glBufferStorage, (GLenum, GLsizeiptr, const void*, GLbitfield))
#define PLOT_GL_DECL(ret, name, args) extern ret (*name) args;
PLOT_GL_FUNCS(PLOT_GL_DECL)
PLOT_GL_OPTIONAL_FUNCS(PLOT_GL_DECL)
#undef PLOT_GL_DECL
} // namespace plot::gl