收口字体渲染路径以降低 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
+91
View File
@@ -28,6 +28,13 @@ namespace
void shutdown() override {}
};
const Core::FramePixel* PresentPixels(Core::DrawContext& ctx, CaptureDisplay& display)
{
ctx.present(&display);
assert(display.framebuffer != nullptr);
return static_cast<const Core::FramePixel*>(display.framebuffer->get_buffer());
}
void TestFramePixelIsRgb565()
{
static_assert(sizeof(Core::FramePixel) == sizeof(uint16_t), "FramePixel must stay RGB565");
@@ -105,6 +112,87 @@ namespace
assert(loaded_pixels[i] == original_pixels[i]);
}
}
void TestDrawTextUsesBitMaskAndColor()
{
const uint8_t glyph_mask[] = { 0x90u };
RenderData::BitmapFont font;
font.mask_bits = glyph_mask;
font.atlas_width = 2;
font.atlas_height = 2;
font.char_w = 2;
font.char_h = 2;
font.columns = 1;
font.first_char = 'A';
Core::DrawContext ctx(2, 2);
ctx.clear_color(RenderData::Color::Black());
ctx.draw_text(font, 0, 0, RenderData::Color::Red(), "A");
CaptureDisplay display;
const Core::FramePixel* pixels = PresentPixels(ctx, display);
assert(pixels[0] == 0xF800u);
assert(pixels[1] == 0x0000u);
assert(pixels[2] == 0x0000u);
assert(pixels[3] == 0xF800u);
}
void TestDrawTextColorChangesPerCallAndBackgroundFill()
{
const uint8_t glyph_mask[] = { 0x90u };
RenderData::BitmapFont font;
font.mask_bits = glyph_mask;
font.atlas_width = 2;
font.atlas_height = 2;
font.char_w = 2;
font.char_h = 2;
font.columns = 1;
font.first_char = 'A';
Core::DrawContext ctx(4, 2);
ctx.clear_color(RenderData::Color::Black());
ctx.draw_text(font, 0, 0, RenderData::Color::White(), RenderData::Color::Blue(), "A");
ctx.draw_text(font, 2, 0, RenderData::Color::Green(), "A");
CaptureDisplay display;
const Core::FramePixel* pixels = PresentPixels(ctx, display);
assert(pixels[0] == 0xFFFFu);
assert(pixels[1] == 0x001Fu);
assert(pixels[2] == 0x07E0u);
assert(pixels[3] == 0x0000u);
assert(pixels[4] == 0x001Fu);
assert(pixels[5] == 0xFFFFu);
assert(pixels[6] == 0x0000u);
assert(pixels[7] == 0x07E0u);
}
void TestDrawTextClipsBitMaskToScreen()
{
const uint8_t glyph_mask[] = { 0x90u };
RenderData::BitmapFont font;
font.mask_bits = glyph_mask;
font.atlas_width = 2;
font.atlas_height = 2;
font.char_w = 2;
font.char_h = 2;
font.columns = 1;
font.first_char = 'A';
Core::DrawContext ctx(2, 2);
ctx.clear_color(RenderData::Color::Black());
ctx.draw_text(font, -1, -1, RenderData::Color::Green(), "A");
ctx.draw_text(font, 3, 3, RenderData::Color::Red(), "A");
CaptureDisplay display;
const Core::FramePixel* pixels = PresentPixels(ctx, display);
assert(pixels[0] == 0x07E0u);
assert(pixels[1] == 0x0000u);
assert(pixels[2] == 0x0000u);
assert(pixels[3] == 0x0000u);
}
}
int main()
@@ -113,6 +201,9 @@ int main()
TestFrameBufferStoresRgb565();
TestDrawSpriteUsesOneBitAlpha();
TestSpriteAssetLoaderRoundTripsRgba5551();
TestDrawTextUsesBitMaskAndColor();
TestDrawTextColorChangesPerCallAndBackgroundFill();
TestDrawTextClipsBitMaskToScreen();
std::cout << "render_pipeline_tests: PASS\n";
return 0;
}