收口字体渲染路径以降低 atlas 体积并直接输出 RGB565

将位图字体从 RGBA8888 atlas 改为 1-bit mask,避免继续为字体存储无效颜色信息。
生成端现在按 row-major、MSB-first 将 8 个像素打包进 1 个 uint8_t;运行时由
draw_text 提供目标颜色,并在 DrawContext 中直接把 bit=1 的像素写入 RGB565 framebuffer。同步补充了回归测试与文档,确保颜色替换、背景填充和裁剪语义保持稳定。

Constraint: 本次只收口字体路径,不改 sprite/tilemap 资源格式
Constraint: 运行时保持纯二值绘制语义,不引入 alpha blending 或抗锯齿
Rejected: 继续兼容 RGBA8888 字体输入 | 会保留双路径复杂度且无实际收益
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: 后续字体资源必须继续使用 row-major + MSB-first 的 1-bit mask;修改位序前先同步生成脚本、运行时读取和测试
Tested: cmake --build build-win --config Release
Tested: build-win/Release/render_pipeline_tests.exe
Tested: ctest -C Release --output-on-failure
Tested: font_atlas_mask 字节数校验(2808 == ceil(208*108/8))
Not-tested: Demo/设备上的人工目视文字显示效果
This commit is contained in:
2026-06-09 08:20:38 +08:00
parent 5ac61dca0e
commit 23a5b50aec
10 changed files with 379 additions and 1451 deletions
+3 -5
View File
@@ -133,11 +133,9 @@ int main(int argc, char* argv[])
Platform::SteadyTimeSource time_source;
RenderData::BitmapFont font;
font.atlas = RenderData::Image(
font_atlas_pixels,
font_atlas_width,
font_atlas_height,
RenderData::PixelFormat::RGBA8888);
font.mask_bits = font_atlas_mask;
font.atlas_width = font_atlas_width;
font.atlas_height = font_atlas_height;
font.char_w = font_char_w;
font.char_h = font_char_h;
font.columns = font_columns;
+51 -18
View File
@@ -325,31 +325,68 @@ namespace Core
}
}
void DrawContext::draw_font_mask_region(const RenderData::BitmapFont& font,
int32_t src_x, int32_t src_y,
int32_t src_w, int32_t src_h,
int32_t dst_x, int32_t dst_y,
uint16_t pixel)
{
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
for (int32_t row = 0; row < src_h; ++row)
{
const int32_t atlas_row = src_y + row;
const int32_t dst_y_abs = dst_y + row;
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w + dst_x;
for (int32_t col = 0; col < src_w; ++col)
{
const int32_t atlas_x = src_x + col;
const int32_t bit_index = atlas_row * font.atlas_width + atlas_x;
const uint8_t packed = font.mask_bits[bit_index >> 3];
const uint8_t bit_mask = static_cast<uint8_t>(0x80u >> (bit_index & 7));
if ((packed & bit_mask) == 0u) continue;
dst_row[col] = pixel;
}
}
}
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
const RenderData::Color& color, const char* text)
{
if (!font.atlas.pixels || !text) return;
if (!font.mask_bits || !text) return;
const uint32_t rgba = color.to_rgba();
const int32_t cw = font.char_w;
const int32_t ch = font.char_h;
const int32_t atlas_w = font.atlas.width;
const uint32_t* src = static_cast<const uint32_t*>(font.atlas.pixels);
const int32_t atlas_w = font.atlas_width;
const int32_t atlas_h = font.atlas_height;
const int32_t screen_w = frameBuffer->get_width();
const int32_t screen_h = frameBuffer->get_height();
if (cw <= 0 || ch <= 0 || font.columns <= 0 || atlas_w <= 0 || atlas_h <= 0) return;
const Core::FramePixel glyph_pixel = Core::rgba_to_frame_pixel(color.to_rgba());
int32_t cursor_x = x;
for (const char* p = text; *p; ++p)
{
const int32_t index = static_cast<int32_t>(*p) - font.first_char;
if (index < 0) { cursor_x += cw; continue; }
if (index < 0)
{
cursor_x += cw;
continue;
}
const int32_t col = index % font.columns;
const int32_t row = index / font.columns;
const int32_t src_x = col * cw;
const int32_t src_y = row * ch;
if (src_x < 0 || src_y < 0 || src_x + cw > atlas_w || src_y + ch > atlas_h)
{
cursor_x += cw;
continue;
}
// 整字符在屏幕外则跳过
if (cursor_x + cw <= 0 || cursor_x >= screen_w || y + ch <= 0 || y >= screen_h)
{
cursor_x += cw;
@@ -362,18 +399,14 @@ namespace Core
if (y + ch > screen_h) sy_end = screen_h - y;
if (cursor_x < 0) sx_start = -cursor_x;
if (cursor_x + cw > screen_w) sx_end = screen_w - cursor_x;
for (int32_t sy = sy_start; sy < sy_end; ++sy)
{
const uint32_t* atlas_row = src + (src_y + sy) * atlas_w;
const int32_t dst_y_abs = y + sy;
for (int32_t sx = sx_start; sx < sx_end; ++sx)
{
const uint32_t pixel = atlas_row[src_x + sx];
if ((pixel & 0xFF) == 0) continue;
frameBuffer->set_pixel_unsafe(cursor_x + sx, dst_y_abs, rgba);
}
}
draw_font_mask_region(font,
src_x + sx_start,
src_y + sy_start,
sx_end - sx_start,
sy_end - sy_start,
cursor_x + sx_start,
y + sy_start,
glyph_pixel);
cursor_x += cw;
}
}
+6
View File
@@ -35,6 +35,12 @@ namespace Core
Rasterizer::Rasterizer* rasterizer;
Rasterizer::TriangleRasterizer* triangleRasterizer;
void draw_font_mask_region(const RenderData::BitmapFont& font,
int32_t src_x, int32_t src_y,
int32_t src_w, int32_t src_h,
int32_t dst_x, int32_t dst_y,
uint16_t pixel);
public:
DrawContext(int32_t width, int32_t height);
~DrawContext();
+14 -2
View File
@@ -1,15 +1,27 @@
#pragma once
#include <cstdint>
#include "Image.h"
namespace RenderData
{
struct BitmapFont
{
Image atlas;
const uint8_t* mask_bits;
int32_t atlas_width;
int32_t atlas_height;
int32_t char_w;
int32_t char_h;
int32_t columns;
int32_t first_char;
BitmapFont()
: mask_bits(nullptr),
atlas_width(0),
atlas_height(0),
char_w(0),
char_h(0),
columns(0),
first_char(0)
{
}
};
}