即使一直是一个人努力。
本文相比前两个较为简单,目标是在之前的基础上增加字体显示基础设置,主要添加了font.rs
文件,并在gpu.rs
中给出了接口。
1. 原理
字体的渲染包括两步
- 生成字体各个字符的bitmap
- 渲染
每个字符都在一个长方形的像素区域内进行渲染,字符bitmap中的每一位都表示了一个像素点,若为1则为需要渲染,若为0则为不需要渲染。不同字符的高度都相同,然而宽度则不一定,字体根据宽度是否相同可以分为两种:等宽字体和非等宽字体。
这里使用等宽字体。各个字符的bitmap使用全局不可变变量进行存储。
每个字符宽8像素,高8像素。共64像素的区域,需要64位的bitmap表示,即8字节。每个字节表示需要渲染的像素区域的一行。
例如对于ASCII 21的感叹号第一个字节为0x18,即表示感叹号的第一行,二进制表示为00011000,可以发现感叹号 “!” 的竖线宽度为2像素。
2. 字模库
首先定义每个字符字模的数据结构:Bitmap
,即一个由8个u8组成的数组,每一个u8用于渲染字模的一行,每个字模共8行。该字模库对外导出根据要渲染的字符ASCII码返回对应字模的接口。
1 |
|
接着借助该字模库导出putc
接口,其中需要在FRAMEBUFFER全局结构中维护当前绘制的横向偏移(voffset_x
)和纵向偏移(voffset_y
)。主要算法逻辑:
CHAR_HEIGHT
和CHAR_WIDTH
分别表示每个字模的高(像素为单位)和宽(像素为单位)- 首先判断当前绘制的行是否超过屏幕,如果要超过则将屏幕当前内容整体向上偏移一行
- 若是回车,则重设
voffset_x = 0
,voffset_y += CHAR_HEIGHT
- 获取要绘制的字模
- 对每一行的每一列像素进行渲染
- 设置(
voffset_x += CHAR_WIDTH
)位置,若宽超过屏幕,则voffset_x = 0
且voffset_y += CHAR_HEIGHT
1 |
|
最后在全局Console
的io::write
,fmt::write_str
方法中把这个接口用上就大功告成。
3. 系统集成
3.1. 何时能够使用println
之前全局Console
上的write
方法都是对uart
的内存映射io地址的写入因此在系统各个子模块初始化前就能使用。然而现在Console
除了写uart
还要写framebuffer
,因此只有在framebuffer
初始化结束后才能使用打印功能。而framebuffer
依赖于系统的内存分配器分配消息缓存,所以现在mem_allocator
初始化的时候也不能使用打印功能。
3.2. 适配虚拟内存系统
ATAGs返回的可用内存不包括系统分配给GPU的部分。这意味着framebuffer不在全局内存管理器的管辖范围内。这意味着下面的好处和坏处:
- pro: 内存管理器的内存分配不会对framebuffer产生影响。同时在“实模式”下可以直接使用framebuffer地址进行读写,无需其他配置。
- con: 为了适配虚拟内存系统,就要一些其他的工作。具体而言,在初始化内核页表的时候,需要对GPU管辖内存空间的页进行映射。我这里直接把0x3C000000以上的页都映射为设备地址,CPU侧就不会对其进行cache缓存。
其实分配给GPU的内存可以通过config.txt
的gpu_mem
参数进行配置。默认情况下,1G的rpi分配了76M
给GPU。具体见Memory options in config.txt
4. 效果
finally,可以渲染字体了哈哈
5. 实现
这一系列大致就结束了,接口和实现参考了很多Printing to a Real Screen。更高级的绘图基础设施比如画曲线,使用double buffer提高性能等等,就到上图形学的课程之后再说吧。。。最近太忙了,心累。。还有其他的proj要肝。偷偷说一句,截至写完本文,backspace我还没处理hhh