refactor(render): TextBackend owns its atlas, and staged glyphs carry a rotation basis
This commit is contained in:
1 parent
90e17a0a11
commit
e074f57ddf
23 files changed
+1073
-227
No files matched your search
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/gl/handle.hpp>
|
||||
#include <plot/math/vec2.hpp>
|
||||
#include <plot/render/text.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
|
||||
/// The GPU side of a `TextBackend`'s atlas.
|
||||
///
|
||||
/// The atlas belongs to the backend, so the renderer does not decide what it
|
||||
/// contains or how big it is: `sync()` uploads whatever `atlas_pixels()` points
|
||||
/// at, as a single-channel `GL_R8` image of `atlas_width()` by `atlas_height()`,
|
||||
/// and re-uploads exactly when the backend's `revision()` changes. Keying that
|
||||
/// cache on the revision rather than on a flag set once is what makes a font
|
||||
/// swap, or a re-bake at another size, visible -- a cached atlas left in place
|
||||
/// draws the previous font's glyphs and nothing in the pixels says why.
|
||||
///
|
||||
/// It is also where the atlas upload's `GL_UNPACK_ALIGNMENT` excursion is
|
||||
/// contained: `Texture::upload_r8` needs 1 for single-channel rows and leaves it
|
||||
/// there, so every upload here saves the caller's value and puts it back --
|
||||
/// whether it runs in `Renderer::init()` or in the middle of a frame, which is
|
||||
/// why the renderer's frame no longer has to carry that value around one.
|
||||
///
|
||||
/// Nearest filtering, always: the shader reads the texel centre of a glyph cell,
|
||||
/// and linear filtering would smear one cell into its neighbour.
|
||||
class TextAtlas {
|
||||
public:
|
||||
TextAtlas() noexcept = default;
|
||||
TextAtlas(const TextAtlas&) = delete;
|
||||
TextAtlas& operator=(const TextAtlas&) = delete;
|
||||
TextAtlas(TextAtlas&&) noexcept = default;
|
||||
TextAtlas& operator=(TextAtlas&&) noexcept = default;
|
||||
~TextAtlas() = default;
|
||||
|
||||
/// Brings the texture up to date with `backend`, and does nothing at all when
|
||||
/// it already is: the same revision as the last successful upload means the
|
||||
/// bytes on the GPU are the ones that revision describes. Any change counts,
|
||||
/// including one that moves the revision backwards, so a backend that
|
||||
/// re-bakes is never mistaken for one that did not.
|
||||
///
|
||||
/// A null atlas, or one with a zero width or height, is legal and means "no
|
||||
/// text": nothing is uploaded, `empty()` stays true and the resulting draw
|
||||
/// stages nothing. The revision is recorded even then, so a backend whose
|
||||
/// atlas never appears is not re-checked every frame -- the contract is that a
|
||||
/// backend which *starts* providing one bumps its revision.
|
||||
///
|
||||
/// False, with `st` filled in, when the driver refuses the texture; the last
|
||||
/// successful upload is left in place and the next call retries.
|
||||
bool sync(const TextBackend& backend, Status* st = nullptr) noexcept;
|
||||
|
||||
/// Binds to `unit` (texture unit 0 by default, which is where the text
|
||||
/// program's sampler points).
|
||||
void bind(gl::GLenum unit = gl::GL_TEXTURE0) const noexcept;
|
||||
|
||||
/// Atlas size in texels; `(0, 0)` while nothing has been uploaded.
|
||||
Vec2 texel_size() const noexcept {
|
||||
return Vec2{static_cast<f32>(width_), static_cast<f32>(height_)};
|
||||
}
|
||||
|
||||
/// True while no atlas is on the GPU: there is no text to draw, and `bind()`
|
||||
/// would bind nothing.
|
||||
bool empty() const noexcept { return texture_.id() == 0; }
|
||||
|
||||
/// The revision of the atlas currently on the GPU. Meaningless before the
|
||||
/// first `sync()`, which is the one call that always uploads.
|
||||
u32 revision() const noexcept { return revision_; }
|
||||
|
||||
private:
|
||||
gl::Texture texture_;
|
||||
u32 revision_ = 0;
|
||||
u32 width_ = 0;
|
||||
u32 height_ = 0;
|
||||
/// False until a revision has been recorded, so a backend whose `revision()`
|
||||
/// is 0 is still uploaded once instead of being mistaken for "already there".
|
||||
bool synced_ = false;
|
||||
};
|
||||
|
||||
} // namespace plot::render
|
||||
Reference in new issue
Block a user