feat(render): anti-aliased embedded Adwaita Mono atlas, generated and committed
This commit is contained in:
1 parent
3ff7b9c978
commit
5ca3587325
17 files changed
+3422
-627
No files matched your search
+36
-20
@@ -6,13 +6,14 @@
|
||||
#include <plot/gl/caps.hpp>
|
||||
#include <plot/gl/handle.hpp>
|
||||
#include <plot/gl/loader.hpp>
|
||||
#include <plot/render/bitmap_font.hpp>
|
||||
#include <plot/render/embedded_mono_font.hpp>
|
||||
#include <plot/render/primitives.hpp>
|
||||
#include <plot/render/program.hpp>
|
||||
#include <plot/render/shaders.hpp>
|
||||
#include <plot/render/text_atlas.hpp>
|
||||
#include <plot/render/vertex_array.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -341,10 +342,10 @@ PLOT_TEST(line_pipeline_draws_an_aa_strip) {
|
||||
|
||||
// The text pipeline end to end: the R8 atlas upload, the per-instance glyph UV
|
||||
// rect, the NEAREST texel-centre sampling, the tint and the same single y flip.
|
||||
// Every one of the cell's 64 pixels is checked against the glyph bitmap, so a
|
||||
// wrong atlas cell, a wrong UV or a transposed axis cannot pass.
|
||||
// Every texel of the cell is checked against the backend's own coverage byte, so
|
||||
// a wrong atlas cell, a wrong UV or a transposed axis cannot pass.
|
||||
PLOT_TEST(text_pipeline_draws_a_glyph) {
|
||||
using plot::render::BitmapFont;
|
||||
using plot::render::EmbeddedMonoFont;
|
||||
plottest::HeadlessContext ctx(256, 256);
|
||||
plot::Status st;
|
||||
PLOT_CHECK(ctx.create(&st));
|
||||
@@ -352,12 +353,12 @@ PLOT_TEST(text_pipeline_draws_a_glyph) {
|
||||
|
||||
// The backend's atlas, through the owner the renderer uses: single channel,
|
||||
// GL_NEAREST, straight alpha, uploaded from the backend's own bytes.
|
||||
BitmapFont font;
|
||||
EmbeddedMonoFont font;
|
||||
plot::render::TextAtlas atlas;
|
||||
PLOT_CHECK(atlas.sync(font, &st));
|
||||
PLOT_CHECK(!atlas.empty());
|
||||
PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast<plot::f32>(BitmapFont::kAtlasWidth), 1e-6);
|
||||
PLOT_CHECK_NEAR(atlas.texel_size().y, static_cast<plot::f32>(BitmapFont::kAtlasHeight), 1e-6);
|
||||
PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast<plot::f32>(font.atlas_width()), 1e-6);
|
||||
PLOT_CHECK_NEAR(atlas.texel_size().y, static_cast<plot::f32>(font.atlas_height()), 1e-6);
|
||||
|
||||
plot::render::TextProgram program;
|
||||
PLOT_CHECK(program.create(&st));
|
||||
@@ -383,29 +384,44 @@ PLOT_TEST(text_pipeline_draws_a_glyph) {
|
||||
|
||||
program.bind();
|
||||
program.set_viewport({static_cast<f32>(ctx.width), static_cast<f32>(ctx.height)});
|
||||
program.set_px_range({static_cast<f32>(BitmapFont::kGlyphPx), static_cast<f32>(BitmapFont::kGlyphPx)});
|
||||
// The backend's own cell, on screen at 1:1: the draw's one rect size.
|
||||
const plot::f32 cell_w = static_cast<plot::f32>(EmbeddedMonoFont::cell_width());
|
||||
const plot::f32 cell_h = static_cast<plot::f32>(EmbeddedMonoFont::cell_height());
|
||||
program.set_px_range({cell_w, cell_h});
|
||||
program.set_glyph_uv(font.cell_uv());
|
||||
program.set_texture_size(atlas.texel_size());
|
||||
program.set_color(plot::Color::hex(0xFF00FF));
|
||||
// White, so the blended pixel value *is* the coverage byte: the shader emits
|
||||
// `vec4(rgb, a * coverage)`, and a white tint over the black clear lands at
|
||||
// `1.0 * coverage` in every channel.
|
||||
program.set_color(plot::Color::hex(0xFFFFFF));
|
||||
atlas.bind();
|
||||
glyphs.bind();
|
||||
plot::gl::glDrawArraysInstanced(plot::gl::GL_TRIANGLE_STRIP, 0, 4, 1);
|
||||
|
||||
// One cell is 8x8 texels and u_px_range is 8, so texel (gx, gy) lands on one
|
||||
// target pixel: Rect x = 64 + gx, and Rect y = 100 + gy is GL row 155 - gy.
|
||||
const plot::u8* rows = font.glyph_bitmap('A');
|
||||
// The cell is cell_w x cell_h texels and u_px_range is the same, so texel
|
||||
// (gx, gy) lands on exactly one target pixel: Rect x = 64 + gx, and Rect y =
|
||||
// 100 + gy is GL row 155 - gy. A transposed axis, a wrong cell or a wrong UV
|
||||
// all move ink away from where the coverage byte says it is.
|
||||
const plot::u32 slot = EmbeddedMonoFont::glyph_index(static_cast<plot::u32>('A'));
|
||||
const plot::Vec2 origin = EmbeddedMonoFont::atlas_origin(slot);
|
||||
const plot::u8* atlas_bytes = font.atlas_pixels();
|
||||
plot::u32 antialiased = 0;
|
||||
plot::u8 px[4] = {};
|
||||
for (plot::u32 gy = 0; gy < BitmapFont::kGlyphPx; ++gy) {
|
||||
for (plot::u32 gx = 0; gx < BitmapFont::kGlyphPx; ++gx) {
|
||||
const bool ink = ((rows[gy] >> gx) & 1u) != 0u;
|
||||
for (plot::u32 gy = 0; gy < EmbeddedMonoFont::cell_height(); ++gy) {
|
||||
for (plot::u32 gx = 0; gx < EmbeddedMonoFont::cell_width(); ++gx) {
|
||||
const plot::u32 ax = static_cast<plot::u32>(origin.x) + gx;
|
||||
const plot::u32 ay = static_cast<plot::u32>(origin.y) + gy;
|
||||
const plot::u8 want = atlas_bytes[static_cast<std::size_t>(ay) * font.atlas_width() + ax];
|
||||
PLOT_CHECK(ctx.read_pixel(64u + gx, 155u - gy, px));
|
||||
if (ink) {
|
||||
PLOT_CHECK(px[0] > 200 && px[1] < 60 && px[2] > 200);
|
||||
} else {
|
||||
PLOT_CHECK(px[0] < 40 && px[2] < 40);
|
||||
}
|
||||
PLOT_CHECK_NEAR(px[0], static_cast<plot::f32>(want), 2.0);
|
||||
PLOT_CHECK_NEAR(px[1], static_cast<plot::f32>(want), 2.0);
|
||||
PLOT_CHECK_NEAR(px[2], static_cast<plot::f32>(want), 2.0);
|
||||
if (want > 0u && want < 255u) ++antialiased;
|
||||
}
|
||||
}
|
||||
// 'A' has grey edge texels and they reached the framebuffer: a 1-bit atlas
|
||||
// could only ever have produced 0 or 255 here.
|
||||
PLOT_CHECK(antialiased > 0u);
|
||||
PLOT_CHECK(plot::gl::glGetError() == plot::gl::GL_NO_ERROR);
|
||||
ctx.destroy();
|
||||
}
|
||||
Reference in new issue
Block a user