Files
plotter/src/render/vertex_array.cpp
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

108 lines
3.7 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#include <plot/render/vertex_array.hpp>
#include <plot/gl/api.hpp>
#include <cstddef>
namespace plot::render {
namespace {
/// Instance data advances one step per instance, never per vertex: both
/// pipelines draw one quad per instance with corners synthesised from
/// `gl_VertexID`, so every attribute has a divisor of 1 (core since GL 3.3).
constexpr gl::GLuint kPerInstance = 1;
} // namespace
bool LineVertexArray::create(Status* st, UploadMode mode) noexcept {
if (!vao_.create(st)) {
return false;
}
if (!buffer_.create(st, mode)) {
vao_.reset(); // do not leave a vertex array behind on the failure path
return false;
}
// Attribute pointers capture the bound GL_ARRAY_BUFFER, so bind both first.
vao_.bind();
buffer_.bind();
// Offsets come from the struct, not from counting floats: the layout is the
// one shaders.hpp declares, and it stays in step with LineInstance.
vao_.attrib(0u, 2, sizeof(LineInstance), offsetof(LineInstance, x0));
vao_.attrib(1u, 2, sizeof(LineInstance), offsetof(LineInstance, x1));
gl::glVertexAttribDivisor(0u, kPerInstance);
gl::glVertexAttribDivisor(1u, kPerInstance);
gl::glBindVertexArray(0); // leave the binding as we found it
epoch_ = buffer_.epoch();
return true;
}
void LineVertexArray::upload(const f32* interleaved, u32 segments) noexcept {
// An array that was never created reports nothing to draw, so a caller that
// ignored create()'s status rasterises nothing instead of stale memory.
if (interleaved == nullptr || vao_.id() == 0) {
instances_ = 0;
return;
}
buffer_.upload(interleaved, segments);
// Only the mapped transfer moves the instances between slots, and the
// attribute offsets have to follow them. The generation changes for a new slot
// or a re-created buffer, never otherwise, so the baseline path re-points at
// most once.
if (buffer_.epoch() != epoch_) {
const usize base = buffer_.slot_offset();
vao_.bind();
buffer_.bind();
vao_.attrib(0u, 2, sizeof(LineInstance), base + offsetof(LineInstance, x0));
vao_.attrib(1u, 2, sizeof(LineInstance), base + offsetof(LineInstance, x1));
epoch_ = buffer_.epoch();
}
instances_ = segments;
}
bool TextVertexArray::create(Status* st, UploadMode mode) noexcept {
if (!vao_.create(st)) {
return false;
}
if (!buffer_.create(st, mode)) {
vao_.reset();
return false;
}
vao_.bind();
buffer_.bind();
// Three attributes now: the cell's corner, its atlas rect and the basis that
// rotates it (see TextInstance). All of them advance once per instance, like
// the line pipeline's, because the corners come from gl_VertexID.
vao_.attrib(0u, 2, sizeof(TextInstance), offsetof(TextInstance, x));
vao_.attrib(1u, 2, sizeof(TextInstance), offsetof(TextInstance, u));
vao_.attrib(2u, 2, sizeof(TextInstance), offsetof(TextInstance, ax));
gl::glVertexAttribDivisor(0u, kPerInstance);
gl::glVertexAttribDivisor(1u, kPerInstance);
gl::glVertexAttribDivisor(2u, kPerInstance);
gl::glBindVertexArray(0);
epoch_ = buffer_.epoch();
return true;
}
void TextVertexArray::upload(const f32* interleaved, u32 glyphs) noexcept {
if (interleaved == nullptr || vao_.id() == 0) {
instances_ = 0;
return;
}
buffer_.upload(interleaved, glyphs);
if (buffer_.epoch() != epoch_) {
const usize base = buffer_.slot_offset();
vao_.bind();
buffer_.bind();
vao_.attrib(0u, 2, sizeof(TextInstance), base + offsetof(TextInstance, x));
vao_.attrib(1u, 2, sizeof(TextInstance), base + offsetof(TextInstance, u));
vao_.attrib(2u, 2, sizeof(TextInstance), base + offsetof(TextInstance, ax));
epoch_ = buffer_.epoch();
}
instances_ = glyphs;
}
} // namespace plot::render