feat(render): style, GLSL line/text pipelines, staging batches, bitmap font
This commit is contained in:
1 parent
9a086b7d69
commit
7cbc1c9955
22 files changed
+1535
No files matched your search
@@ -6,6 +6,7 @@ namespace plot {
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
using u8 = std::uint8_t;
|
||||
using i32 = std::int32_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
@@ -139,6 +139,7 @@ enum : GLenum {
|
||||
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 */ \
|
||||
@@ -153,6 +154,7 @@ enum : GLenum {
|
||||
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)) \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <plot/core/color.hpp>
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/gl/api.hpp>
|
||||
@@ -103,6 +104,13 @@ class Program final : public Handle<Program> {
|
||||
|
||||
/// Sets a two-component float uniform.
|
||||
void set_uniform(const char* name, Vec2 value) const noexcept;
|
||||
/// Sets a four-component float uniform, e.g. the colour of a pipeline.
|
||||
void set_uniform(const char* name, Color value) const noexcept;
|
||||
|
||||
/// Sets an integer uniform, e.g. a sampler's texture unit. Deliberately not
|
||||
/// an overload of the float setter: an untyped literal such as `0` must not
|
||||
/// turn a float uniform into a `glUniform1i` call.
|
||||
void set_uniform_int(const char* name, i32 value) const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/math/vec2.hpp>
|
||||
#include <plot/render/text.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// 8x8 bitmap font, the text backend of the renderer.
|
||||
///
|
||||
/// The glyphs are 1-bit, one byte per row, bit 0 the leftmost pixel; the table
|
||||
/// and its provenance are in `src/render/bitmap_font.cpp`. Nothing here needs a
|
||||
/// font file, FreeType or stb_truetype: `plot_core` stays dependency-free.
|
||||
///
|
||||
/// Metrics are proportional, not fixed width: an advance is the glyph's ink
|
||||
/// width plus one pixel of spacing, floored at two pixels, computed once when
|
||||
/// the font is constructed from the same bitmaps the atlas uploads. Whitespace
|
||||
/// is a blank glyph with a nonzero advance, and every codepoint without a glyph
|
||||
/// draws a visible box, so a missing glyph is obvious rather than silently
|
||||
/// blank.
|
||||
///
|
||||
/// Metrics are in unscaled font pixels, one per glyph texel. The text pipeline
|
||||
/// draws a cell `u_px_range` pixels across, so a run at another size measures
|
||||
/// `measure(utf8) * u_px_range.x / kGlyphPx`; `TextBatch::add_text` takes that
|
||||
/// cell size and applies the same scale to the pen it advances.
|
||||
class BitmapFont final : public TextBackend {
|
||||
public:
|
||||
// Atlas geometry. It is part of the render pipeline's contract: the text
|
||||
// vertex shader expands a glyph cell by `u_px_range` pixels and reads the cell
|
||||
// at the instance's texel origin, so the atlas is a grid of 8x8 cells whose
|
||||
// width must stay a whole number of cells.
|
||||
static constexpr u32 kGlyphPx = 8; ///< glyph cell edge, texels
|
||||
static constexpr u32 kFirstChar = 32; ///< U+0020
|
||||
static constexpr u32 kAsciiGlyphs = 95; ///< U+0020..U+007E
|
||||
static constexpr u32 kDegreeSlot = kAsciiGlyphs; ///< U+00B0 DEGREE SIGN
|
||||
static constexpr u32 kMicroSlot = kAsciiGlyphs + 1u; ///< U+00B5 MICRO SIGN
|
||||
static constexpr u32 kOhmSlot = kAsciiGlyphs + 2u; ///< U+03A9 OHM
|
||||
static constexpr u32 kFallbackSlot = kAsciiGlyphs + 3u; ///< missing-glyph box
|
||||
static constexpr u32 kGlyphCount = kAsciiGlyphs + 4u;
|
||||
static constexpr u32 kAtlasCols = 16;
|
||||
static constexpr u32 kAtlasRows = 7;
|
||||
static constexpr u32 kAtlasWidth = kAtlasCols * kGlyphPx; ///< 128 texels
|
||||
static constexpr u32 kAtlasHeight = kAtlasRows * kGlyphPx; ///< 56 texels
|
||||
static_assert(kAtlasCols * kAtlasRows >= kGlyphCount, "atlas too small for the glyph set");
|
||||
|
||||
/// Reported by `next_codepoint` for a byte sequence that is not valid UTF-8.
|
||||
/// It is not a Unicode scalar value, so it can only map to the fallback box.
|
||||
static constexpr u32 kInvalidCodepoint = 0xFFFFFFFFu;
|
||||
|
||||
/// Decodes one UTF-8 sequence from `p` and advances `p` past it — one byte
|
||||
/// when the sequence is malformed, so a damaged label still terminates. The
|
||||
/// text batch and `measure` share it, which is what keeps a staged run exactly
|
||||
/// `measure`-wide.
|
||||
static u32 next_codepoint(const char*& p) noexcept;
|
||||
|
||||
BitmapFont() noexcept;
|
||||
|
||||
/// Width of `utf8`, the sum of the glyph advances: zero for the empty string
|
||||
/// and for null. The ASCII path allocates nothing.
|
||||
f32 measure(const char* utf8) const override;
|
||||
|
||||
/// Cell height in pixels (one glyph row of text occupies `height()` pixels).
|
||||
f32 height() const override;
|
||||
/// Top of the cell to the baseline, in pixels.
|
||||
f32 ascent() const override;
|
||||
|
||||
/// Advance of one ASCII character in pixels. A byte that is not printable
|
||||
/// ASCII reports the missing-glyph box, matching what `measure` gives that
|
||||
/// byte on its own.
|
||||
f32 advance(char c) const noexcept;
|
||||
|
||||
/// Advance of a decoded Unicode codepoint, in pixels.
|
||||
f32 advance_codepoint(u32 codepoint) const noexcept;
|
||||
|
||||
/// Glyph slot for a codepoint: printable ASCII is its own slot, the three SI
|
||||
/// codepoints `format_si` emits map to real glyphs, and anything else maps to
|
||||
/// the visible missing-glyph box.
|
||||
static u32 glyph_index(u32 codepoint) noexcept;
|
||||
|
||||
/// Rows in a glyph bitmap, always `kGlyphPx`.
|
||||
u32 glyph_rows(char c) const noexcept;
|
||||
|
||||
/// The `kGlyphPx` rows of one ASCII character's bitmap, top row first.
|
||||
const u8* glyph_bitmap(char c) const noexcept;
|
||||
|
||||
/// The `kGlyphPx` rows of a glyph slot.
|
||||
static const u8* glyph_bitmap_of(u32 slot) noexcept;
|
||||
|
||||
/// The packed atlas image, ready for `plot::gl::Texture::upload_r8`: rows of
|
||||
/// `kAtlasWidth` bytes, top row first, 255 for ink and 0 for background
|
||||
/// (straight alpha, no dilation or gamma applied).
|
||||
static const u8* atlas_image() noexcept;
|
||||
|
||||
/// Texel origin of a glyph slot in the atlas; this is the per-instance `a_uv`
|
||||
/// the text pipeline reads.
|
||||
static Vec2 atlas_origin(u32 slot) noexcept;
|
||||
|
||||
private:
|
||||
f32 advances_[kGlyphCount];
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/gl/handle.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// Per-instance payload of the line pipeline: one segment in data space. It
|
||||
/// mirrors the interleaved floats `LineBatch::data()` produces, which is why the
|
||||
/// attribute offsets in vertex_array.cpp come from `offsetof` rather than from
|
||||
/// hand-written numbers.
|
||||
struct LineInstance {
|
||||
f32 x0;
|
||||
f32 y0;
|
||||
f32 x1;
|
||||
f32 y1;
|
||||
};
|
||||
|
||||
/// Per-instance payload of the text pipeline: the glyph quad's top-left corner
|
||||
/// in Rect pixels plus the glyph's atlas texel origin.
|
||||
struct TextInstance {
|
||||
f32 x;
|
||||
f32 y;
|
||||
f32 u;
|
||||
f32 v;
|
||||
};
|
||||
|
||||
static_assert(sizeof(LineInstance) == 4 * sizeof(f32), "line instance must stay 4 floats");
|
||||
static_assert(sizeof(TextInstance) == 4 * sizeof(f32), "text instance must stay 4 floats");
|
||||
|
||||
namespace detail {
|
||||
|
||||
/// The growth half of an instance buffer, shared by both pipelines so neither
|
||||
/// duplicates the GL ownership or the "grow only when the frame needs it" rule.
|
||||
class InstanceStore {
|
||||
public:
|
||||
/// Creates the vertex buffer. Reports `ErrorCode::Unsupported` while the GL
|
||||
/// entry points are unresolved, `GLFailure` if the driver hands out no name.
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
/// Uploads `count` instances of `instance_bytes`. The store only reallocates
|
||||
/// when the frame's workload outgrows it, so steady-state frames write into
|
||||
/// the existing allocation.
|
||||
void upload(const void* data, u32 count, usize instance_bytes) noexcept;
|
||||
|
||||
void bind() const noexcept { buffer_.bind(); }
|
||||
|
||||
private:
|
||||
gl::Buffer buffer_;
|
||||
u32 capacity_ = 0; ///< instances the store can hold
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Instance buffer of the line pipeline: one `LineInstance` per segment.
|
||||
class LineBuffer {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept { return store_.create(st); }
|
||||
void upload(const f32* interleaved, u32 count) noexcept {
|
||||
store_.upload(interleaved, count, sizeof(LineInstance));
|
||||
}
|
||||
void bind() const noexcept { store_.bind(); }
|
||||
|
||||
private:
|
||||
detail::InstanceStore store_;
|
||||
};
|
||||
|
||||
/// Instance buffer of the text pipeline: one `TextInstance` per glyph.
|
||||
class TextBuffer {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept { return store_.create(st); }
|
||||
void upload(const f32* interleaved, u32 count) noexcept {
|
||||
store_.upload(interleaved, count, sizeof(TextInstance));
|
||||
}
|
||||
void bind() const noexcept { store_.bind(); }
|
||||
|
||||
private:
|
||||
detail::InstanceStore store_;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/math/vec2.hpp>
|
||||
#include <plot/render/bitmap_font.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);
|
||||
|
||||
/// Segments staged so far, one instance each.
|
||||
u32 segment_count() const noexcept { return static_cast<u32>(v_.size() / 4u); }
|
||||
/// Vertices the pipeline will rasterise: two per staged segment.
|
||||
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 quad's top-left corner in pixels, top-left origin
|
||||
/// like `plot::Rect`, plus the glyph's atlas texel origin, interleaved as
|
||||
/// `x,y,u,v` — the layout the text shader declares (see buffer.hpp).
|
||||
class TextBatch {
|
||||
public:
|
||||
/// Starts a frame: empties the batch, keeps its capacity.
|
||||
void begin() noexcept;
|
||||
/// Same as `begin()`.
|
||||
void clear() noexcept;
|
||||
|
||||
/// Stages one glyph cell at `pos_px` (its top-left corner), reading the cell
|
||||
/// from `slot` in the font atlas (see `BitmapFont::atlas_origin`). `slot`
|
||||
/// indexes the atlas, not a codepoint.
|
||||
void add_glyph(Vec2 pos_px, u32 slot);
|
||||
|
||||
/// Stages every codepoint of `utf8` and returns the pen past the last glyph,
|
||||
/// so the run is exactly `font.measure(utf8) * px_per_cell /
|
||||
/// BitmapFont::kGlyphPx` pixels wide — the width the draw covers when it is
|
||||
/// told the same cell size as `u_px_range`.
|
||||
///
|
||||
/// `px_per_cell` is the on-screen size of one glyph cell: pass
|
||||
/// `BitmapFont::kGlyphPx` for 1:1 text, and anything else to scale the run.
|
||||
/// The glyph quad itself is sized by the shader's `u_px_range`, which must be
|
||||
/// the same value. A null or empty string stages nothing and returns `pen_px`.
|
||||
Vec2 add_text(Vec2 pen_px, const char* utf8, const BitmapFont& font,
|
||||
f32 px_per_cell = static_cast<f32>(BitmapFont::kGlyphPx));
|
||||
|
||||
/// Glyphs staged so far, one instance each.
|
||||
u32 glyph_count() const noexcept { return static_cast<u32>(v_.size() / 4u); }
|
||||
|
||||
/// Interleaved `x,y,u,v`, four floats per staged glyph.
|
||||
const std::vector<f32>& data() const noexcept { return v_; }
|
||||
|
||||
private:
|
||||
std::vector<f32> v_;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include <plot/core/color.hpp>
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/gl/handle.hpp>
|
||||
#include <plot/math/vec2.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// Linked line program and its uniform surface.
|
||||
///
|
||||
/// It owns the `plot::gl::Program` and knows the GLSL pair (shaders.hpp), so no
|
||||
/// caller spells out a uniform name that could drift from the shader. Creating
|
||||
/// it propagates what the GL layer reports: `ErrorCode::Unsupported` while
|
||||
/// `plot::gl::available()` is false, `GLFailure` plus the driver's log when the
|
||||
/// GLSL does not compile or link.
|
||||
class LineProgram {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
bool valid() const noexcept { return program_.id() != 0; }
|
||||
void bind() const noexcept { program_.use(); }
|
||||
|
||||
/// `u_scale`: data space to pixels.
|
||||
void set_scale(Vec2 data_to_px) const noexcept;
|
||||
/// `u_offset`: pixel offset added after `set_scale`.
|
||||
void set_offset(Vec2 px) const noexcept;
|
||||
/// `u_viewport`: render target size in pixels.
|
||||
void set_viewport(Vec2 px) const noexcept;
|
||||
/// `u_width`: stroke width in pixels.
|
||||
void set_width(f32 px) const noexcept;
|
||||
/// `u_color`: stroke colour, alpha included.
|
||||
void set_color(Color color) const noexcept;
|
||||
|
||||
private:
|
||||
gl::Program program_;
|
||||
};
|
||||
|
||||
/// Linked text program and its uniform surface. It fails exactly the way
|
||||
/// `LineProgram::create()` does, propagating `Unsupported` and `GLFailure`.
|
||||
///
|
||||
/// `create()` also points the `u_atlas` sampler at texture unit 0, which is the
|
||||
/// unit `plot::gl::Texture::bind()` defaults to, and leaves this program bound.
|
||||
/// The atlas itself stays with the caller, who owns its lifetime.
|
||||
class TextProgram {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
bool valid() const noexcept { return program_.id() != 0; }
|
||||
void bind() const noexcept { program_.use(); }
|
||||
|
||||
/// `u_viewport`: render target size in pixels.
|
||||
void set_viewport(Vec2 px) const noexcept;
|
||||
/// `u_px_range`: pixels per glyph cell, i.e. the on-screen size of one glyph.
|
||||
void set_px_range(Vec2 px) const noexcept;
|
||||
/// `u_tex_size`: atlas size in texels.
|
||||
void set_texture_size(Vec2 texels) const noexcept;
|
||||
/// `u_color`: tint colour, alpha included.
|
||||
void set_color(Color color) const noexcept;
|
||||
|
||||
private:
|
||||
gl::Program program_;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// GLSL source of the instanced antialiased line pipeline.
|
||||
///
|
||||
/// The vertex shader takes one segment per instance (`a_p0` at location 0,
|
||||
/// `a_p1` at location 1, both with a vertex attribute divisor of 1), expands it
|
||||
/// in pixel space along its normal by `u_width * 0.5 + 1.0` pixels of AA fringe,
|
||||
/// and emits a four-vertex `GL_TRIANGLE_STRIP` quad whose corners come from
|
||||
/// `gl_VertexID` (0 and 1 are the two sides of `a_p0`, 2 and 3 of `a_p1`). It
|
||||
/// outputs `v_dist`, the signed distance from the segment axis in pixels, and
|
||||
/// `v_along`, the distance travelled from `a_p0` in pixels.
|
||||
///
|
||||
/// Uniforms: `u_scale` (vec2, data space to pixels), `u_offset` (vec2, pixels),
|
||||
/// `u_viewport` (vec2, pixels), `u_width` (float, pixels), `u_color` (vec4).
|
||||
/// `plot::Rect` is top-left origin while GL is bottom-left, so the y axis is
|
||||
/// flipped exactly once, here.
|
||||
///
|
||||
/// The fragment shader discards outside the fringe and ramps alpha over the last
|
||||
/// pixel of the half width with `smoothstep`, which is what keeps a 1px trace
|
||||
/// from aliasing into dashes.
|
||||
extern const char* const kLineVS;
|
||||
extern const char* const kLineFS;
|
||||
|
||||
/// GLSL source of the instanced text pipeline.
|
||||
///
|
||||
/// One glyph cell per instance: `a_pos` at location 0 is the quad's top-left
|
||||
/// corner in Rect pixels and `a_uv` at location 1 is the glyph's atlas texel
|
||||
/// origin, both with a vertex attribute divisor of 1. The quad grows by
|
||||
/// `u_px_range` pixels per cell and the fragment shader samples the
|
||||
/// single-channel `GL_R8` atlas at the nearest texel centre, so `GL_NEAREST`
|
||||
/// never lands on a cell boundary. `u_color` tints the straight alpha.
|
||||
///
|
||||
/// Uniforms: `u_viewport` (vec2, pixels), `u_px_range` (vec2, pixels per glyph
|
||||
/// cell), `u_tex_size` (vec2, atlas texels), `u_atlas` (sampler2D, unit 0),
|
||||
/// `u_color` (vec4).
|
||||
extern const char* const kTextVS;
|
||||
extern const char* const kTextFS;
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <plot/core/color.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// Every colour and metric the renderer draws with. Defaults are the dark theme
|
||||
/// the plan's render smoke test asserts against; a caller swaps a field and the
|
||||
/// next frame picks it up, so nothing here is baked into a shader.
|
||||
struct Style {
|
||||
Color background = Color::hex(0x101418);
|
||||
Color grid_major = Color::hex(0x2A3038);
|
||||
Color grid_minor = Color::hex(0x1C2126);
|
||||
Color axis_line = Color::hex(0x8A94A6);
|
||||
Color axis_text = Color::hex(0xC8D0DC);
|
||||
Color cursor = Color::hex(0xFFB020);
|
||||
Color measure_fill = Color::hex(0xFFB020, 0.15f);
|
||||
f32 axis_font_px = 12.0f;
|
||||
f32 grid_label_pad = 4.0f;
|
||||
f32 line_width_px = 1.5f;
|
||||
f32 dense_threshold_spp = 1.5f; ///< spec §4.3: samples per pixel above which
|
||||
///< a trace is drawn from the LOD pyramid.
|
||||
f32 tick_min_px = 60.0f; ///< minimum spacing between major ticks.
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <plot/core/types.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// Text metrics the plot layer lays axes and labels out with. It is a pure
|
||||
/// interface so the layout code never depends on the bitmap font, and so a
|
||||
/// caller can measure with a host toolkit's font instead.
|
||||
///
|
||||
/// Text is passed as UTF-8, not as a byte-per-character string: `plot::format_si`
|
||||
/// emits U+00B5, U+00B0 and U+03A9 for micro, degree and ohm, and an
|
||||
/// implementation that ignores that prints mojibake.
|
||||
class TextBackend {
|
||||
public:
|
||||
TextBackend() noexcept = default;
|
||||
TextBackend(const TextBackend&) = default;
|
||||
TextBackend& operator=(const TextBackend&) = default;
|
||||
virtual ~TextBackend() = default;
|
||||
|
||||
/// Width of `utf8` in the same units as `height()` (pixels for the bitmap
|
||||
/// font). Zero for the empty string.
|
||||
virtual f32 measure(const char* utf8) const = 0;
|
||||
|
||||
/// Line height: the cell a single line of text occupies.
|
||||
virtual f32 height() const = 0;
|
||||
|
||||
/// Distance from the top of the cell to the baseline, so a caller can align
|
||||
/// text bottoms instead of cell bottoms.
|
||||
virtual f32 ascent() const = 0;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/gl/handle.hpp>
|
||||
#include <plot/render/buffer.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// Vertex array plus instance buffer of the line pipeline.
|
||||
///
|
||||
/// The attribute layout is fixed by the line shader (see shaders.hpp): `a_p0` at
|
||||
/// location 0 and `a_p1` at location 1, two floats each, advancing once per
|
||||
/// instance. A caller never writes a location, stride or offset; it stages a
|
||||
/// `LineBatch` and uploads its `data()`.
|
||||
class LineVertexArray {
|
||||
public:
|
||||
/// Creates the vertex array, the instance buffer and the attribute layout.
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
/// Uploads `segments` instances from interleaved `x0,y0,x1,y1` floats and
|
||||
/// records how many the next draw should rasterise.
|
||||
void upload(const f32* interleaved, u32 segments) noexcept;
|
||||
|
||||
void bind() const noexcept { vao_.bind(); }
|
||||
bool valid() const noexcept { return vao_.id() != 0; }
|
||||
u32 instance_count() const noexcept { return instances_; }
|
||||
|
||||
private:
|
||||
LineBuffer buffer_;
|
||||
gl::VertexArray vao_;
|
||||
u32 instances_ = 0;
|
||||
};
|
||||
|
||||
/// Vertex array plus instance buffer of the text pipeline: `a_pos` at location 0
|
||||
/// and the glyph's atlas texel origin `a_uv` at location 1, two floats each,
|
||||
/// advancing once per instance.
|
||||
class TextVertexArray {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
/// Uploads `glyphs` instances from interleaved `x,y,u,v` floats.
|
||||
void upload(const f32* interleaved, u32 glyphs) noexcept;
|
||||
|
||||
void bind() const noexcept { vao_.bind(); }
|
||||
bool valid() const noexcept { return vao_.id() != 0; }
|
||||
u32 instance_count() const noexcept { return instances_; }
|
||||
|
||||
private:
|
||||
TextBuffer buffer_;
|
||||
gl::VertexArray vao_;
|
||||
u32 instances_ = 0;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
Reference in new issue
Block a user