Files
plotter/include/plot/render/primitives.hpp
T
Martino Ferrari ed3a8ce645 licence: MIT LICENSE and SPDX identifiers in every first-party file
The repository had no licence at all, so every source was all-rights-reserved
by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino
Ferrari) and a two-line SPDX header to each of the 113 first-party sources --
48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the
grant travels with each file rather than relying on the root document alone.

Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely
vendored code is untouched: third_party/stb_truetype.h carries no header of
ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py
emit() now writes the two lines into its header comment and the file was
regenerated -- gen_atlas.py --check passes and the regeneration adds only those
comment lines to the previously committed file.

README's provenance section now states MIT and links LICENSE.

No code, test or public API change.
2026-09-28 22:13:40 +02:00

138 lines
6.3 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#pragma once
#include <plot/core/types.hpp>
#include <plot/math/vec2.hpp>
#include <plot/render/text.hpp>
#include <vector>
namespace plot::render {
/// CPU-side staging for one instanced-AA line draw call.
///
/// `begin()` starts a frame and `clear()` empties the batch; neither releases
/// the store, and both leave capacity alone so a steady-state frame re-uses the
/// previous allocation instead of reallocating per frame. The renderer re-uses
/// one batch for the whole run and uploads `data()` once per frame.
///
/// The payload is interleaved `x0,y0,x1,y1` per segment — exactly the
/// per-instance attribute layout the line shader declares (see buffer.hpp and
/// shaders.hpp), so the intersection of `data()` with an `f32` span can be
/// uploaded verbatim.
class LineBatch {
public:
/// Starts a frame: empties the batch, keeps its capacity.
void begin() noexcept;
/// Same as `begin()`, so the render loop can read as begin/stage/draw.
void clear() noexcept;
/// Stages one segment: one instance, two quad corners per end.
void add_segment(Vec2 a, Vec2 b);
/// Stages `n - 1` segments joining consecutive points. A null pointer or
/// fewer than two points stages nothing.
void add_polyline(const Vec2* pts, u32 n);
/// Same, for `n` points already interleaved as `x,y` floats -- the layout
/// `data::Series::gather` appends to, so a gathered window is staged without a
/// copy or a reinterpret_cast of its storage.
void add_polyline_xy(const f32* xy, u32 n);
/// Segments staged so far, one instance each.
u32 segment_count() const noexcept { return static_cast<u32>(v_.size() / 4u); }
/// Staging vertices, two per staged segment. The pipeline draws one instanced
/// quad per segment (its four `GL_TRIANGLE_STRIP` corners come from
/// `gl_VertexID`), so the instance count a draw passes is `vertex_count() / 2`
/// — handing `vertex_count()` to `glDrawArraysInstanced` would rasterise
/// twice the geometry the batch staged.
u32 vertex_count() const noexcept { return segment_count() * 2u; }
/// Interleaved `x0,y0,x1,y1`, four floats per staged segment.
const std::vector<f32>& data() const noexcept { return v_; }
private:
std::vector<f32> v_;
};
/// CPU-side staging for one instanced text draw call.
///
/// Like `LineBatch` it keeps its capacity across frames. Each staged glyph is
/// one instance: the glyph cell's top-left corner in pixels, the glyph rect's
/// normalised top-left in the atlas and the cell's rotation basis, interleaved as
/// `x,y,u,v,ax,ay` — the layout the text shader declares (see `TextInstance` in
/// buffer.hpp).
///
/// Every glyph a batch stages is read through one atlas rect of one size — its
/// cell (see `TextBackend::cell_uv`) — so that size is declared once
/// (`set_cell()`) and handed to the draw, rather than riding along with every
/// instance. In a debug build every staged glyph is checked against it.
class TextBatch {
public:
/// Starts a frame: empties the batch and forgets the declared cell, keeps the
/// capacity.
void begin() noexcept;
/// Same as `begin()`.
void clear() noexcept;
/// Declares the atlas cell the glyphs staged from here on are drawn in -- the
/// backend's `cell_uv()`. `add_text()` does this itself from the backend it is
/// handed; a caller staging glyphs one at a time says it here, so the draw's
/// single rect size comes from the backend rather than from whichever glyph was
/// staged first. A debug build asserts that no second, different cell arrives
/// in one batch.
void set_cell(Vec2 cell_uv) noexcept;
/// Stages one glyph cell whose *pen* is `pen_px`: the cell's top-left corner is
/// the pen moved by the glyph's bearing (see `Glyph`), scaled and measured
/// along the run's own axes, so a glyph keeps its place inside a rotated run.
///
/// `px` is the scale: on-screen pixels per font pixel, the factor `measure()`
/// is multiplied by — `1.0` draws the glyphs at the size the backend
/// rasterised them. `axis` is the direction the run advances in, in pixel
/// space; it rotates the cell (see `TextInstance`), and is normalised here, so
/// any non-zero direction works and a zero or non-finite one means "unrotated".
void add_glyph(Vec2 pen_px, const Glyph& glyph, f32 px = 1.0f,
Vec2 axis = {1.0f, 0.0f});
/// Stages every codepoint of `utf8` from `pen_px` and returns the pen past the
/// last glyph, so the run is exactly `font.measure(utf8) * px` long measured
/// along `axis` — the length the plot layer reserved for the label, at the size
/// the text pipeline then draws it.
///
/// The codepoints and the advances come from the backend itself, through
/// `next_codepoint()` and `glyph()`, so a run stages exactly what `measure()`
/// summed. A codepoint the backend has no glyph for stages nothing and moves
/// the pen nowhere — a run is never wider than the glyphs it can draw. A null
/// or empty string stages nothing and returns `pen_px`.
Vec2 add_text(const char* utf8, Vec2 pen_px, f32 px, const TextBackend& font,
Vec2 axis = {1.0f, 0.0f});
/// Glyphs staged so far, one instance each.
u32 glyph_count() const noexcept { return static_cast<u32>(v_.size() / 6u); }
/// Interleaved `x,y,u,v,ax,ay`, six floats per staged glyph.
const std::vector<f32>& data() const noexcept { return v_; }
/// The cell the staged glyphs are drawn in: what `set_cell()` declared, or --
/// for a caller that stages raw glyphs without ever declaring one -- the rect
/// size of the first glyph staged, since nothing else can be the authority
/// there. The renderer declares the cell from the backend it draws with, so it
/// never depends on that fallback. `(0, 0)` while nothing is staged.
Vec2 glyph_cell_uv() const noexcept { return cell_uv_; }
private:
/// Stages one cell whose basis the caller has already normalised, so a run and
/// the pen it advances share one rounding of the axis.
void add_cell(Vec2 pen_px, const Glyph& glyph, f32 px, Vec2 unit_ux);
std::vector<f32> v_;
Vec2 cell_uv_{};
/// False until a cell arrives -- from `set_cell()`, or from the first glyph
/// staged by a caller that declared none. It keeps a zero-sized cell
/// distinguishable from "nothing said yet".
bool cell_declared_ = false;
};
} // namespace plot::render