fix(render): gate TextAtlas::sync on the loader, and cover three review findings
Review of the atlas refactor found one Important and three Minor issues: - Important: TextAtlas::sync called glGetIntegerv before any available() check, so a host that synced before plot::gl::load() -- which is legal for a public class Tasks 2 and 8 build backends on -- segfaulted on a null entry point instead of receiving the Status the interface promises. It now follows the handle-constructor convention (available(), then the entry point itself) and reports Unsupported. - empty() no longer means "a GL name exists": releasing one is a glDeleteTextures call, so the no-atlas path forgets the atlas and only resets the texture when there is a table to release it with. - end_frame's comment still claimed the frame hands back the unpack alignment init() set, contradicting the code below it and renderer.hpp. - The orientation case's texel probe mapped the atlas' u along the cell's +v axis and vice versa, which is a no-op for its two diagonal probes -- so it could not fail on a transposed UV mapping, and its own comment misstated the rule. Probes now follow the shader's rule and include an off-diagonal pair. - The null-glyph path of add_text (and its twin in walk_text_clipped) was unreached by any case. Cases: text_atlas_reports_a_status_when_the_entry_points_are_missing, add_text_skips_a_codepoint_the_backend_cannot_draw.
This commit is contained in:
1 parent
e1f6b61b7d
commit
3ff7b9c978
5 files changed
+230
-16
No files matched your search
@@ -326,11 +326,14 @@ void Renderer::end_frame() noexcept {
|
||||
gl_error_ = true;
|
||||
}
|
||||
// Leave the host's context as close to neutral as a frame can: nothing bound to
|
||||
// a pipeline stage, off every state bit the frame turned on, and the two values
|
||||
// the frame *changed* rather than merely used handed back -- the unpack
|
||||
// alignment `init()` set for the atlas, and the host's clear colour. What
|
||||
// remains is the viewport (the caller's to set for its next pass), the pixel-pack
|
||||
// alignment a PPM dump may have set, and the vertex buffers' own contents.
|
||||
// a pipeline stage, off every state bit the frame turned on, and the one value
|
||||
// the frame *changed* rather than merely used handed back -- the host's clear
|
||||
// colour. What remains is the viewport (the caller's to set for its next pass),
|
||||
// the pixel-pack alignment a PPM dump may have set, and the vertex buffers' own
|
||||
// contents. `GL_UNPACK_ALIGNMENT` is not in this list: `render::TextAtlas`
|
||||
// saves and restores it around every atlas upload, so the frame never leaves it
|
||||
// moved for the host's own uploads to inherit (see end_frame's contract in
|
||||
// renderer.hpp).
|
||||
gl::glBindVertexArray(0);
|
||||
gl::glUseProgram(0);
|
||||
gl::glBindBuffer(gl::GL_ARRAY_BUFFER, 0);
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
#include <plot/render/text_atlas.hpp>
|
||||
|
||||
#include <plot/gl/api.hpp>
|
||||
#include <plot/gl/loader.hpp>
|
||||
|
||||
namespace plot::render {
|
||||
namespace {
|
||||
|
||||
/// The loader's own wording, because `Status::message` is a non-owning pointer to
|
||||
/// a string this file has to keep alive (`plot::gl::load()`'s private constant is
|
||||
/// not reachable from here).
|
||||
constexpr const char* kNoLoader = "plot::gl::load() has not resolved the GL entry points";
|
||||
|
||||
bool fail(Status* st, ErrorCode code, const char* message) noexcept {
|
||||
if (st != nullptr) {
|
||||
st->code = code;
|
||||
st->message = message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TextAtlas::sync(const TextBackend& backend, Status* st) noexcept {
|
||||
if (synced_ && revision_ == backend.revision()) {
|
||||
@@ -13,17 +30,34 @@ bool TextAtlas::sync(const TextBackend& backend, Status* st) noexcept {
|
||||
const u32 height = backend.atlas_height();
|
||||
if (pixels == nullptr || width == 0u || height == 0u) {
|
||||
// "No text", not a failure: record the revision so the check above settles,
|
||||
// and release the texture as well as the size -- an atlas a backend has
|
||||
// stopped publishing must not stay bound, or a draw would sample glyphs that
|
||||
// are no longer its and divide by a zero texture size doing it.
|
||||
// and stop reporting an atlas -- an atlas a backend has stopped publishing
|
||||
// must not stay bound, or a draw would sample glyphs that are no longer its
|
||||
// and divide by a zero texture size doing it.
|
||||
revision_ = backend.revision();
|
||||
synced_ = true;
|
||||
has_atlas_ = false;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
texture_.reset();
|
||||
// Releasing the name is a `glDeleteTextures` call, so it happens only when
|
||||
// there is a table to make it with. That costs nothing when the loader never
|
||||
// resolved -- no name can have been created then (`sync` refuses before the
|
||||
// first `gl*` call) -- and a later sync re-uses whatever name is left.
|
||||
if (gl::available()) {
|
||||
texture_.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This class is public and a host may sync it before `plot::gl::load()`: while
|
||||
// the entry-point table is unresolved no `gl*` call may be made at all (see
|
||||
// `plot::gl::loader.hpp` -- the pointers are null until then), so the guard is
|
||||
// checked before the first of them and reports the same `Unsupported` every
|
||||
// fallible `plot::gl` handle constructor reports. The entry point beside it is
|
||||
// the convention those constructors follow.
|
||||
if (!gl::available() || gl::glGetIntegerv == nullptr) {
|
||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||
}
|
||||
|
||||
// `upload_r8` needs GL_UNPACK_ALIGNMENT 1 for single-channel rows and leaves it
|
||||
// there; the caller's value is this call's to put back, wherever it runs.
|
||||
gl::GLint host_alignment = 4;
|
||||
@@ -44,11 +78,16 @@ bool TextAtlas::sync(const TextBackend& backend, Status* st) noexcept {
|
||||
}
|
||||
revision_ = backend.revision();
|
||||
synced_ = true;
|
||||
has_atlas_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextAtlas::bind(gl::GLenum unit) const noexcept { texture_.bind(unit); }
|
||||
void TextAtlas::bind(gl::GLenum unit) const noexcept {
|
||||
if (has_atlas_) {
|
||||
texture_.bind(unit);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace plot::render
|
||||
Reference in new issue
Block a user