1. 项目概述:为什么我们需要硬件性能计数器?
在嵌入式图形开发里,最让人头疼的莫过于性能问题。界面卡顿、动画掉帧,你明明觉得代码逻辑没问题,但就是跑不快。这时候,光靠软件打点计时(
printf
或者高精度定时器)往往力不从心,因为它无法告诉你硬件内部到底在“忙”什么:是GPU在等内存数据?是纹理缓存命中率太低?还是光栅化单元已经满负荷运转?
硬件性能计数器(Performance Counter)就是为了解决这个“黑盒”问题而生的。它就像是给图形引擎装上了一套精密的仪表盘,可以实时、无侵入地监测其内部各个功能单元的工作状态。以瑞萨RA8P1微控制器内置的2D绘图引擎(DRW)为例,它提供了两个独立的性能计数器(PERFCOUNT1和PERFCOUNT2)。通过配置对应的触发寄存器(PERFTRIGGER),你可以选择让计数器对特定事件进行累加,例如:
- 2D绘图引擎活跃周期 :直接反映引擎的负载率。
- 帧缓冲区的读写访问及缓存命中/未命中 :这是诊断内存带宽瓶颈的关键。
- 纹理读取访问及缓存命中/未命中 :直接影响贴图绘制的效率。
- 被剔除的不可见像素 :帮助你了解过度绘制(Overdraw)的情况。
- 显示列表读取器活跃周期 :评估命令提交的瓶颈。
掌握这些数据,你就能从“凭感觉优化”转向“用数据驱动优化”。比如,你发现
PERFCOUNT1
(配置为纹理读取未命中)的数值异常高,那么优化方向就很明确了:调整纹理格式、启用纹理压缩、或者优化纹理的寻址模式以减少缓存抖动。这比盲目地尝试各种优化手段要高效得多。
本文将深入RA8P1 DRW引擎的内部,不仅详解性能计数器的配置与使用,更会拆解其独特的基于“半平面”(Half-Plane)方程的硬件渲染管线。你会看到,从一条简单的直线到一个复杂的带透明度的旋转纹理,硬件是如何一步步计算出来的。理解这套管线,你才能更好地驾驭性能计数器提供的数据,真正发挥出这块硬件的全部潜力。
2. 性能计数器:硬件级的性能剖析工具
性能计数器并非RA8P1独有,它是现代处理器和专用加速器中常见的调试与优化设施。其核心思想是将关键的内部信号(事件)作为触发源,驱动一个计数器进行累加。RA8P1的DRW引擎将其实现为两个可编程的32位计数器。
2.1 寄存器详解:PERFTRIGGER与PERFCOUNT
驱动性能计数器,本质上就是配置两个寄存器组。
PERFTRIGGERk (k=1,2) - 性能计数器触发选择寄存器 这个寄存器决定了计数器“数什么”。它是一个32位寄存器,但只使用低16位(PERFTRIGGER1)和高16位(PERFTRIGGER2),分别控制两个计数器。
-
位域
:
-
PERFTRIGGER1[15:0]: 选择递增PERFCOUNT1寄存器的事件。 -
PERFTRIGGER2[15:0]: 选择递增PERFCOUNT2寄存器的事件。
-
-
关键功能值解析
:
-
0x0000: 禁用 该性能计数器。这是复位后的默认值。 -
0x0001: 选择 2D绘图引擎活跃周期 。只要引擎在执行绘图操作(非空闲),每个时钟周期计数器加1。这是衡量引擎利用率最直接的指标。 -
0x0002/0x0003: 选择 帧缓冲区读/写访问 。每次引擎读取或写入帧缓冲区像素,对应计数器加1。结合渲染的像素总数,可以计算平均每个像素的读写次数。 -
0x0004: 选择 纹理读取访问 。每次从纹理内存(或缓存)中读取纹素(Texel),计数器加1。 -
0x0005: 选择 因Alpha值为0%而被剔除的不可见像素 。这反映了在像素着色阶段早期因完全透明而被丢弃的像素数量,有助于优化绘制顺序和Alpha测试。 -
0x0008至0x000D: 选择 帧缓冲区和纹理缓存的命中与未命中 。这是性能调优的黄金指标。高未命中率意味着数据不在快速缓存中,需要从更慢的主存读取,会显著拉低性能。 -
0x001F: 选择 每个时钟周期 。这相当于将一个计数器配置为一个高精度定时器,可以用来测量一段绘图操作的确切时钟周期数。
-
注意 :
PERFTRIGGER寄存器是只写(W)的。这意味着你无法通过读取它来确认当前配置,因此在驱动程序中,通常需要在全局变量或数据结构中保存当前的配置状态。
PERFCOUNTk (k=1,2) - 性能计数器值寄存器 这是一个简单的32位可读写(R/W)寄存器,用于存储对应事件的累计发生次数。
-
操作
:
-
初始化
:在开始性能测量前,通过向
PERFCOUNTk写入0x00000000来将其清零。 -
启动
:配置好
PERFTRIGGERk,选择要监控的事件。 - 执行 :运行你想要分析的图形绘制代码。
-
读取
:操作完成后,读取
PERFCOUNTk的值,即为该事件在测量期间发生的总次数。
-
初始化
:在开始性能测量前,通过向
- 注意事项 :计数器达到32位最大值(约42.9亿次)后会回绕到0。对于长时间运行的监控,需要在软件层面处理溢出。通常的作法是设置一个定时中断,定期(例如每帧)读取并累加计数器值到更大的变量中,然后重置硬件计数器。
2.2 实战:配置与使用流程
下面是一个简化的C语言伪代码示例,展示了如何使用性能计数器来测量一次矩形填充操作的纹理缓存效率。
// 假设 DRW 寄存器基地址已映射
volatile uint32_t *DRW_PERFTRIGGER = (uint32_t*)(DRW_BASE + 0xD4);
volatile uint32_t *DRW_PERFCOUNT1 = (uint32_t*)(DRW_BASE + 0xCC);
volatile uint32_t *DRW_PERFCOUNT2 = (uint32_t*)(DRW_BASE + 0xD0);
void measure_texture_cache_performance(void) {
uint32_t texture_hits_before, texture_misses_before;
uint32_t texture_hits_after, texture_misses_after;
uint32_t total_accesses, hit_rate;
// 1. 配置计数器1监控纹理读取命中,计数器2监控纹理读取未命中
*DRW_PERFTRIGGER = (0x000C << 16) | 0x0008; // PERFTRIGGER2=0x000C, PERFTRIGGER1=0x0008
// 2. 清零计数器
*DRW_PERFCOUNT1 = 0;
*DRW_PERFCOUNT2 = 0;
// 3. (可选)读取一次作为“之前”的值(此时应为0)
texture_hits_before = *DRW_PERFCOUNT1;
texture_misses_before = *DRW_PERFCOUNT2;
// 4. 执行待测的绘图操作,例如绘制一个带纹理的四边形
draw_textured_quad(...);
// 5. 读取“之后”的值
texture_hits_after = *DRW_PERFCOUNT1;
texture_misses_after = *DRW_PERFCOUNT2;
// 6. 计算命中率和总访问量
total_accesses = (texture_hits_after - texture_hits_before) +
(texture_misses_after - texture_misses_before);
if (total_accesses > 0) {
hit_rate = ((texture_hits_after - texture_hits_before) * 100) / total_accesses;
printf("纹理缓存: 命中 %u 次, 未命中 %u 次, 命中率 %u%%\n",
texture_hits_after - texture_hits_before,
texture_misses_after - texture_misses_before,
hit_rate);
} else {
printf("本次操作未发生纹理读取。\n");
}
}
2.3 性能分析策略与常见问题
策略一:定位瓶颈
-
首先测量
0x0001(引擎活跃周期)。如果一段复杂的绘制操作期间,该计数器值接近耗时时钟周期数,说明引擎本身是瓶颈。 -
如果引擎活跃度不高,但帧率依然低,则可能是内存瓶颈。接着测量帧缓冲区和纹理的读写访问(
0x0002,0x0003,0x0004)以及它们的未命中率(0x0009,0x000B,0x000D)。高未命中率是内存子系统瓶颈的明确信号。
策略二:优化验证
在进行了某项优化(例如,将纹理从
ARGB8888
改为
RGB565
,或启用了纹理压缩)之后,重新测量相关计数器。有效的优化应该能观察到纹理读取访问次数减少,或纹理缓存命中率提升。
常见问题与排查:
-
计数器无变化
:首先检查
PERFTRIGGER是否已正确配置(非0值)。其次,确认你测量的绘图操作确实会触发所选事件。例如,纯色填充不会触发纹理读取事件。 - 数值异常大或溢出 :测量区间可能过长。考虑缩短测量范围(如一帧内),或增加软件侧的溢出处理逻辑。
- 数据难以解读 :性能计数器提供的是原始数据。需要结合你对绘制操作的理解来分析。例如,绘制一个100x100像素的矩形,理想的帧缓冲区写入次数应该是10000次。如果远大于此,可能意味着存在过度绘制或驱动bug。
实操心得 :在驱动初始化时,可以为每个重要的绘制函数(如
blit,draw_line,fill_polygon)添加可选的性能测量钩子。这样在调试版本中,可以轻松地输出每个绘图调用的性能数据,快速定位到具体的性能热点函数。
3. RA8P1 2D绘图引擎渲染管线深度解析
性能计数器告诉我们“哪里慢”,而理解渲染管线则告诉我们“为什么慢”。RA8P1的DRW引擎采用了一种非常优雅且高效的硬件设计,它没有传统的“固定功能”三角形设置和光栅化单元,而是使用了一套基于 半平面方程 的通用光栅化系统。
3.1 核心思想:半平面(Half-Plane)与限幅器(Limiter)
这是理解整个管线的钥匙。一条直线可以将平面划分为两个半平面。对于一个凸多边形(如三角形),它的内部区域可以看作是构成其各条边的所有直线所定义的半平面的 交集 。
RA8P1的硬件实现了最多6个这样的“半平面判断器”,称为
限幅器(Limiter)
。每个限幅器负责计算一个值
f(x, y) = ax + by + c
(线性情况)或
f(x, y) = ax² + by² + cx + dy + f
(二次情况,用于圆/椭圆)。这个
f(x, y)
的几何意义是点
(x, y)
到该边(或曲线)的
有符号距离
。
-
当
f(x, y) > 0,点在半平面“内侧”。 -
当
f(x, y) < 0,点在半平面“外侧”。 -
当
f(x, y) = 0,点在边界上。
对于三角形,我们需要3个限幅器。硬件会扫描目标矩形区域(Bounding Box)内的每一个像素,为每个限幅器计算
f(x, y)
。然后通过
组合器(Combiner)
单元(执行
min
或
max
操作)将这些限幅器的输出合并。最终,只有当所有限幅器都判断像素在内侧(即合并后的
alpha > 0
)时,该像素才会被送入后续的纹理、着色、混合管线进行处理。这个合并后的
alpha
值,可以直接用作像素的透明度,从而实现
硬件抗锯齿
——边界像素的
alpha
值在0到1之间平滑过渡。
3.2 管线全景:从顶点到像素
整个DRW的渲染管线可以概括为以下几个阶段,它与性能计数器监控的点息息相关:
-
命令与顶点提交(CPU/Driver)
:软件驱动计算图元的边界框、限幅器参数(
START,XADD,YADD)以及纹理坐标变换矩阵。这些参数通过寄存器或显示列表提交给DRW。PERFTRIGGER选择0x0007(显示列表读取器活跃周期)可以监控此阶段的效率。 -
光栅化(Rasterization)
:这是核心阶段。硬件遍历边界框内的每个像素,并行计算所有激活的限幅器的值,并通过组合器生成覆盖
alpha值。此阶段对应PERFTRIGGER的0x0001(引擎活跃周期)。 -
纹理获取(Texture Fetch)
:对于需要贴图的图元,硬件根据当前像素的纹理坐标,从纹理内存中获取纹素。这里涉及纹理缓存。
PERFTRIGGER的0x0004(纹理读取访问)、0x000C(命中)、0x000D(未命中)在此阶段被触发。 -
颜色计算与混合(Color & Blending)
:获取到的纹理颜色(或常量颜色)与顶点颜色(如果有)进行计算。然后,根据
alpha值(来自覆盖alpha、纹理alpha、常量alpha)与帧缓冲区中已有的像素颜色进行混合。帧缓冲区的读写(0x0002,0x0003,0x0008,0x0009,0x000A,0x000B)主要发生在此阶段。 - 像素写入(Pixel Write) :混合后的最终颜色被转换回帧缓冲区格式(如RGB565),并写入帧缓冲区内存。
3.3 限幅器设置详解:直线与圆的例子
驱动的工作就是为每个图元正确设置限幅器寄存器。我们来看两个最基础的例子。
案例一:绘制一条从P0(x0, y0)到P1(x1, y1)的直线(作为有宽度的线段边界)
-
计算边向量和法向量
:
Δx = x1 - x0,Δy = y1 - y0边的法向量(未归一化)为n = (-Δy, Δx)。这个向量垂直于边。 -
计算限幅器参数
(线性模式):
-
START = -x0 * Δy + y0 * Δx(这是点P0到直线f(x,y)=0的有符号距离的倍数) -
XADD = -Δy -
YADD = Δx
-
-
归一化(用于抗锯齿)
:
如果需要光滑的边缘,需要将法向量归一化(变为单位长度)。设
length = sqrt(Δx² + Δy²)。-
START = (-x0 * Δy + y0 * Δx) / length -
XADD = -Δy / length -
YADD = Δx / length
-
硬件会从边界框的左上角开始,用
START
初始化当前距离值。每向右移动一个像素,距离值加上
XADD
;每向下移动一行,
START
加上
YADD
,然后当前行重新从新的
START
开始。这种增量计算方式极其高效。
案例二:绘制一个圆心在(s, t),半径为r的实心圆
圆的方程是
(x-s)² + (y-t)² - r² = 0
。这是一个二次方程
f(x,y) = ax² + by² + cx + dy + f
。
-
匹配系数
:
-
a = 1 -
b = 1 -
c = -2s -
d = -2t -
f = s² + t² - r²
-
-
计算限幅器参数
(需要两个限幅器协作,L1和L2):
-
L1START = f = s² + t² - r² -
L1XADD = c + a = -2s + 1 -
L1YADD = d + b = -2t + 1 -
L2START = L1XADD = -2s + 1 -
L2XADD = 2a = 2 -
L2YADD = 2b = 2
-
硬件在扫描时,不仅更新
f(x,y)
的值(
L1START
),还会更新它在x方向的变化率(
L1XADD
,其值由
L2START
跟踪,而
L2START
自身又以恒定值
L2XADD=2
变化)。这就是用线性硬件迭代计算二次方程的精妙之处。
3.4 高级特性:带滤波与抗锯齿
-
带滤波(Band Filter)
:限幅器1和2的输出在钳位(Clamp)前,可以经过一个带滤波单元。这相当于将一条“硬”边(内侧>0,外侧<=0)扩展成一个有一定宽度的“软”区域。参数
w定义了这条带的宽度。这对于绘制 具有宽度的线条 特别有用,无需设置多个三角形来模拟。 -
抗锯齿(Anti-Aliasing)
:这是该架构的天然优势。当限幅器输出未启用阈值模式(Threshold Mode)时,其输出的就是有符号距离场(SDF)值。经过钳位到[0,1]区间后,边界像素的
alpha值在0到1之间连续变化。这个alpha可以直接用于混合阶段,实现完美的硬件抗锯齿,无需多重采样(MSAA),节省了带宽和计算量。 - 阈值模式(Threshold Mode) :当不需要抗锯齿时(例如绘制UI图标时要求锐利的边缘),可以启用阈值模式。此时,限幅器输出值大于0.5的被视为1(完全在内),小于等于0.5的被视为0(完全在外)。这能提高渲染速度,并避免混合带来的颜色渗透。
4. 纹理与像素处理流水线
光栅化确定了“画哪里”,纹理和颜色处理则决定了“画什么”和“怎么画”。RA8P1 DRW的纹理管线支持从1bpp到32bpp的丰富格式,并集成了颜色查找表(CLUT)、游程编码(RLE)压缩、颜色键控(Color Keying)和双线性过滤(Bilinear Filtering)等高级功能。
4.1 纹理格式与颜色查找表(CLUT)
为了节省内存带宽和存储空间,DRW支持多种压缩或索引格式的纹理。
| 纹理内存占用 | 格式 | 说明与使用场景 |
|---|---|---|
| 1/2/4/8 bpp | CLUT (1)/(2)/(4)/(8) |
索引色
。纹理数据存储的是索引值(1/2/4/8位),通过
CLOFFSET
偏移后,在256项的CLUT中查找实际ARGB或RGB565颜色。非常适合颜色数有限的UI图标、字体位图。
|
| 8 bpp | A (8) | 仅Alpha通道 。纹理只提供透明度信息,颜色由寄存器指定。用于蒙版、软阴影等。 |
| 8 bpp | ACLUT (44) | Alpha + 4位索引色 。高4位为Alpha,低4位为CLUT索引。在需要透明度和有限颜色时,比16位ARGB4444节省一半空间。 |
| 16 bpp | RGB565, ARGB4444, ARGB1555 | 高保真压缩 。RGB565无Alpha,ARGB4444各通道4位,ARGB1555是5位RGB+1位Alpha(透明/不透明二值)。游戏和UI常用。 |
| 24 bpp | RGB888 (RLE) | 真彩色,仅支持RLE压缩 。节省空间但解码需开销。 |
| 32 bpp | ARGB8888 | 无损格式 。质量最高,占用空间最大,用于需要高质量Alpha渐变的纹理。 |
CLUT配置流程 :
-
设置
CONTROL2.CLUTFORMAT选择CLUT颜色格式(ARGB8888或RGB565)。 -
将
TEXCLADDR寄存器设置为CLUT内存区域的起始地址。 -
循环将颜色值写入
TEXCLDATA寄存器。硬件会自动递增TEXCLADDR,指向下一项。 -
在纹理控制寄存器中,设置纹理格式为CLUT模式,并配置
CLOFFSET(如果需要)。
注意事项 :CLUT是共享资源。当切换使用不同CLUT的纹理时,必须确保CLUT内容已更新。一种高效的做法是为所有小型纹理(如图标)打包成一个图集(Texture Atlas),并共享一个全局CLUT。
4.2 游程编码(RLE)单元
RLE是一种简单的无损压缩算法,适用于连续相同颜色较多的图像(如卡通图像、UI元素)。DRW硬件集成了RLE解码器,支持类Targa格式的RLE压缩。
-
工作原理
:RLE数据由一系列数据包组成。
- 行程包(Run-length Packet) :第一个字节的最高位为1,低7位表示重复次数-1,后面跟着 一个 要重复的像素值。
- 原始包(Raw Packet) :第一个字节的最高位为0,低7位表示后续像素个数-1,后面跟着对应数量的原始像素值。
-
使用要点
:
-
启用RLE需设置
CONTROL2.RLEENABLE = 1并配置RLEPIXELWIDTH。 - RLE编码数据必须在内存中按字(4字节)对齐 。
-
关键警告
:必须在
每个
新的RLE纹理开始绘制前和结束后,执行
纹理缓存刷新操作
(
CACHECTL.CFLUSHTX = 1)。这是因为RLE解码是流式的,硬件预读可能会超出压缩数据边界,必须用全为1的填充字(32个)来保护内存末尾,防止非法访问。
-
启用RLE需设置
4.3 颜色键控与双线性过滤
-
颜色键控(Color Keying)
:一种简单的透明度处理方式。通过
COLKEY寄存器设定一个“透明色”。当纹理单元读取到的像素颜色与透明色完全匹配时,硬件会将其Alpha和RGB通道都设为0,使其完全透明。这常用于从矩形纹理中裁剪出不规则形状(如圆形图标),并且后续仍可进行全局Alpha混合。 注意 :颜色键控发生在纹理过滤之前。 - 双线性过滤(Bilinear Filtering) :当纹理被拉伸、旋转时,纹理坐标通常不是整数。双线性过滤通过对最近的4个纹素进行加权平均,来平滑纹理,避免出现锯齿状的像素块。DRW的纹理单元可以在X和Y方向独立启用过滤。当两个方向都启用时,即为标准的双线性过滤。更高级的,如果使用两个独立的纹理单元(DRW支持),甚至可以模拟 三线性过滤 (Trilinear Filtering),用于Mipmap层级间的平滑过渡,提升视觉质量。
4.4 颜色转换与混合
DRW内部所有颜色计算都以 32位ARGB8888格式 进行。这意味着:
- 输入转换 :任何非ARGB8888格式的纹理或颜色,在进入处理管线前都会被上转换为ARGB8888。例如,RGB565的蓝色通道会被复制到Alpha通道(除非被替换)。
- 内部处理 :Alpha混合、过滤等所有操作都在高精度下进行。
- 输出转换 :最终结果再被下转换为帧缓冲区格式(如RGB565或ARGB4444)后写入内存。
Alpha混合公式
是图形学的核心。DRW允许为颜色通道和Alpha通道
独立设置混合公式
。常见的“Over”混合(前景覆盖背景)公式为:
Result = (Source * Source_Alpha) + (Dest * (1 - Source_Alpha))
硬件通过配置混合因子寄存器,可以实现多种混合模式,如加性混合、乘性混合等。
5. 驱动层优化与实战经验
理解了硬件原理,最终要落地到驱动和应用的优化上。RA8P1的DRW驱动设计直接影响性能。
5.1 驱动优化策略
-
批量绘制与显示列表
:避免频繁地通过CPU配置寄存器来绘制单个图元。应尽可能将多个绘制命令组织成
显示列表
(Command List),一次性提交给DRW。这减少了CPU开销和总线竞争。性能计数器
0x0007(显示列表读取器活跃周期)可以帮你评估命令提交效率。 - 数据对齐与突发传输 :确保纹理和帧缓冲区数据在内存中按32位(4字节)对齐。这允许ARM Cortex-M85内核(或DMA控制器)使用最高效的突发传输来读写数据,充分利用总线带宽。
-
纹理缓存友好性
:
- 纹理图集 :将大量小纹理合并成一张大纹理,能极大提高缓存命中率,减少纹理切换开销。
- Mipmap链 :对于需要缩放的纹理,使用Mipmap。当纹理被大幅缩小时,硬件会自动读取更低层级的Mipmap,这不仅能提升视觉质量(避免摩尔纹),更能提高缓存效率,因为所需的数据量更少。
-
格式选择
:在质量可接受的前提下,优先使用
RGB565、ARGB1555或CLUT格式,它们能减少50%甚至更多的内存带宽消耗。
-
利用硬件特性
:
- 矩形填充与拷贝优化 :驱动在检测到填充或拷贝矩形操作时,应使用32位并行光栅化模式。对于8bpp或16bpp格式,驱动内部会进行对齐处理,实现每个时钟周期处理2个或4个像素,性能成倍提升。
- 早期深度/Alpha测试 :虽然DRW没有传统的深度缓冲区,但可以利用“不可见像素”(Alpha为0%)计数器来监控过度绘制。合理的绘制顺序(由远及近)和提前进行Alpha测试(在驱动中判断),可以减少无效的像素处理。
5.2 常见问题排查实录
下表总结了使用RA8P1 DRW时可能遇到的典型问题及排查思路:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 绘制结果错乱,出现错误颜色或位置 |
1. 限幅器参数计算错误。
2. 纹理坐标设置错误。 3. 颜色格式配置不匹配(如FB是RGB565,却按ARGB8888写入)。 |
1. 使用简单图元(如纯色矩形)测试,隔离问题。
2. 检查驱动中计算
START/XADD/YADD
的公式,特别是归一化部分。
3. 核对
CONTROL2.READFORMAT
和
WRITEFORMAT
寄存器配置。
|
| 性能远低于预期 |
1. 内存带宽瓶颈(缓存未命中高)。
2. 驱动未启用优化路径(如矩形填充未用32位模式)。 3. 过度绘制严重。 |
1. 使用性能计数器检查
FB/TEX Read Miss
和
Invisible Pixels
。
2. 检查驱动逻辑,确认对
fill_rect
和
copy_rect
走了优化分支。
3. 优化场景图,减少重叠绘制。 |
| 启用RLE后系统崩溃或数据错误 |
1. RLE数据未字对齐。
2. 纹理缓存未在RLE纹理前后刷新。 3. RLE数据末尾未填充32个全1的字。 |
1. 确保
TEXORIGIN
地址是4字节对齐的。
2. 在绘制RLE纹理前后,执行
CACHECTL.CFLUSHTX = 1
。
3. 在RLE压缩数据末尾添加足够的填充(0xFFFFFFFF)。 |
| 抗锯齿边缘有杂色或不够平滑 |
1. 限幅器参数未正确归一化。
2. 混合公式配置有误,或输出格式精度不足(如RGB565)。 |
1. 确认在设置抗锯齿的限幅器时,使用了归一化的
XADD/YADD
(除以边长)。
2. 确保混合模式正确,并考虑在高质量需求下使用ARGB8888帧缓冲区。 |
| 颜色键控(Color Key)不生效 |
1.
COLKEY
寄存器值设置错误。
2. 纹理格式不匹配(例如,纹理带Alpha,但COLKEY比较的是RGB)。 3. 颜色键控功能未启用。 |
1. 确认
COLKEY
的值与纹理中“透明色”的
内部表示
一致(已是ARGB8888格式)。
2. 检查颜色键控使能位是否设置。 |
5.3 一个完整的优化案例:游戏精灵(Sprite)渲染
假设你在开发一个2D游戏,需要每秒渲染数百个带透明背景的精灵。
-
初始方案
:每个精灵是单独的
ARGB8888PNG图片,使用颜色键控去除背景,每帧逐个调用draw_textured_quad。 - 性能问题 :纹理读取次数极高,缓存命中率低,内存带宽压力大。
-
优化步骤
:
-
纹理图集
:将所有精灵打包到一张(或几张)大纹理中。绘制时只需切换纹理坐标,而非整个纹理对象。
性能计数器验证
:观察
Texture Read Accesses次数大幅下降。 -
格式转换
:将图集纹理转换为
ARGB1555或RGB565+Color Key。如果颜色数少于256,甚至可以使用CLUT8。 性能计数器验证 :观察Texture Read Accesses计数不变,但总线的数据量减少,帧时间缩短。 - 启用硬件抗锯齿 :为精灵的边缘启用限幅器的抗锯齿模式(禁用阈值模式)。这能显著提升视觉质量,且硬件开销极小。
-
批处理
:将一帧内所有精灵的绘制命令(变换后的顶点、纹理坐标)收集到一个显示列表中,一次性提交。
性能计数器验证
:
Display List Reader Active Cycles占比增加,但整体帧时间减少,CPU占用率下降。 -
分析过度绘制
:使用
PERFTRIGGER=0x0005监控被剔除的不可见像素。如果数值很高,考虑对精灵进行粗略的层级排序(如按Y轴),从远到近绘制,但需权衡排序CPU开销与像素处理节省。
-
纹理图集
:将所有精灵打包到一张(或几张)大纹理中。绘制时只需切换纹理坐标,而非整个纹理对象。
性能计数器验证
:观察
经过这轮优化,你不仅能获得更高的帧率,还能通过性能计数器的数据,量化每一项优化带来的具体收益。这种数据驱动的开发方式,是打造高性能嵌入式图形应用的关键。
171

被折叠的 条评论
为什么被折叠?



