[ARM-2D 专题]6.脏矩形定义的宏使用技巧和分析
ARM-2d之所以能够高效的进行屏幕绘制,脏矩形的使用起到了巨大作用,功不可没。
简单介绍一下何谓脏矩形:
详细可以参考:如何用脏矩形优化显示帧率
在一帧画面的绘制的时候,我们只绘制画面中变化的部分,可以大大的减少显示屏幕刷新带来的数据传送的工作量,从而提高绘制效率。在资源及其贫乏和受限的嵌入式小系统中,要实现非常多的特效(比如局部滚动,局部动画,旋转,淡入淡出等),使用这个技巧和功能是必不可少。
要使用这个功能,我们必须指出当前画面中的那些区域是需要更新的(第一次刷新除外),也就是定义好脏矩形,从而接下来的相关API在绘制图形的时候,才会对区域进行剪切,通过一些列复杂的计算,得到最终需要绘制的区域。
ARM2D提供了几个相关的宏来帮助我们简单的定义一个脏矩形链表,从而提供给场景播放器使用:
//定义一个脏矩形数组
#define IMPL_ARM_2D_REGION_LIST(__NAME, ...) \
__IMPL_ARM_2D_REGION_LIST(__NAME,##__VA_ARGS__)
//增加一个脏矩形
#define ADD_REGION_TO_LIST(__NAME, ...) \
__ADD_REGION_TO_LIST(__NAME, ##__VA_ARGS__)
//增加最后一个脏矩形(如果只有一个,那就只用这个宏增加脏矩形)
#define ADD_LAST_REGION_TO_LIST(__NAME, ...) \
__ADD_LAST_REGION_TO_LIST(__NAME, ##__VA_ARGS__)
//结束脏矩形定义
#define END_IMPL_ARM_2D_REGION_LIST(...) \
};
我们来看看使用这几个宏定义一个脏矩形数组的一个完整例子:
/*! define dirty regions */
IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions, static)
/* a dirty region to be specified at runtime*/
ADD_REGION_TO_LIST(s_tDirtyRegions,
0,
),
ADD_REGION_TO_LIST(s_tDirtyRegions,
.tLocation = {
.iX = 0,
.iY = 0,
},
.tSize = {
.iWidth = 0,
.iHeight = 8,
},
),
/* add the last region:
* it is the top left corner for text display
*/
ADD_LAST_REGION_TO_LIST(s_tDirtyRegions,
.tLocation = {
.iX = 0,
.iY = 0,
},
.tSize = {
.iWidth = 0,
.iHeight = 8,
},
),
END_IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions)
这几个宏的使用,简化了我们定义脏矩形的工作,但是同时,对于初次接触的用户,也不免有点云里雾里的感觉,同时也比较好奇,它究竟干了什么工作?是怎么实现的呢?下面我们就来个庖丁解牛,一刀一刀的解剖一下,揭开庐山真面目:
先看第一个定义脏矩形的宏的实现:
#define __IMPL_ARM_2D_REGION_LIST(__NAME, ...) \
enum { \
__NAME##_offset = __COUNTER__, \
}; \
__VA_ARGS__ \
arm_2d_region_list_item_t __NAME[] = {
#define IMPL_ARM_2D_REGION_LIST(__NAME, ...) \
__IMPL_ARM_2D_REGION_LIST(__NAME,##__VA_ARGS__)
如果我们把上面例子代码IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions, static)展开,就得到如下的代码:
enum { \
s_tDirtyRegions_offset = __COUNTER__, \
}; \
static arm_2d_region_list_item_t s_tDirtyRegions[] = {
这个是不是没有任何奇特的地方,搞C语言的老铁们,就一眼看明白了,它就是声明了一个arm_2d_region_list_item_t类型的static类型的数组(“开头部分”)。
这里唯一的一个技巧就是使用了一个我们并不常见和多用的一个编译器的预定义宏__COUNTER__。
__COUNTER__是什么?简单科普一下:
- 基本概念:__COUNTER__是 GNU 编译器(如 GCC)的一个非标准扩展特性,它代表一个整数值,初始值通常为0,每遇到一次该宏就会自动加1
- 使用场景:这个宏常被用来防止重复声明冲突,例如在定义多个模板实例或类时,可以通过结合 ## 运算符来生成唯一的名称,从而避免命名冲突
- 唯一标识符:COUNTER 可以与 ## 运算符配合使用,以构建唯一的标识符。例如,可以将任意一个标识符与 COUNTER
合并,形成一个新的唯一标识符 - 注意事项:在使用 COUNTER 时,需要确保它不会在包含预编译头文件之前被展开,否则预编译的头文件将不会被使用
好啦,了解了上面的硬核知识,后,上面的定义我们进一步展开就得到如下代码(假定编译到这里的此时此刻,COUNTER=100)
enum {
s_tDirtyRegions_offset = 100,
};
static arm_2d_region_list_item_t s_tDirtyRegions[] = {
至此,我们得到一个匿名枚举类型的定义,s_tDirtyRegions_offset =100,这个常量的作用,在后面形成链表的时候起到至关重要的作用,具体应用暂且不表,我们继续看看增加一个脏矩形元素到数组的宏是如何工作的:
增加一个脏矩形的宏ADD_REGION_TO_LIST定义如下:
#define __ADD_REGION_TO_LIST(__NAME, ...) \
{ \
.ptNext = (arm_2d_region_list_item_t *) \
&(__NAME[__COUNTER__ - __NAME##_offset]), \
.tRegion = { \
__VA_ARGS__ \
}, \
}
#define ADD_REGION_TO_LIST(__NAME, ...) \
__ADD_REGION_TO_LIST(__NAME, ##__VA_ARGS__)
我们将例子中的宏"ADD_REGION_TO_LIST(s_tDirtyRegions,0,),"展开就得到如下的代码:
{
.ptNext = (arm_2d_region_list_item_t *)
&(s_tDirtyRegions[__COUNTER__ - s_tDirtyRegions_offset ]),
.tRegion = {
0,
},
},
我们利用前面的__COUNTER__知识知道,这个宏被展开的时候,__COUNTER__会等于101(前面假定了脏矩形定义中展开的时候等于100),那么动物们就可以求出来这个常量:COUNTER - s_tDirtyRegions_offset =1,从而上面的代码被转换为:
{
.ptNext = (arm_2d_region_list_item_t *) &(s_tDirtyRegions[1]),
.tRegion = { 0,},
},
这段代码的意思就是将素组的第一个arm_2d_region_list_item_t元素的ptNext指针,指向了该数组的第二个元素的其实地址,形成一个单向链表,同时,将tRegion按照传入的参数初始化。
如果有多个脏矩形需要添加,继续使用宏ADD_REGION_TO_LIST添加即可。
最后来看看脏矩形的最后一个元素的添加宏ADD_LAST_REGION_TO_LIST的定义:
#define __ADD_LAST_REGION_TO_LIST(__NAME, ...) \
{ \
.ptNext = NULL, \
.tRegion = { \
__VA_ARGS__ \
}, \
}
#define ADD_LAST_REGION_TO_LIST(__NAME, ...) \
__ADD_LAST_REGION_TO_LIST(__NAME, ##__VA_ARGS__)
同样的方法,我们展开该宏,得到如下代码:
{
.ptNext = NULL,
.tRegion = {
.tLocation = {
.iX = 0,
.iY = 0,
},
.tSize = {
.iWidth = 0,
.iHeight = 8,
},},
},
该宏和ADD_REGION_TO_LIST的唯一区别就是对ptNext 指针的初始化,因为后面没有任何元素了,所以ptNext被初始化为NULL。
元素的添加完成了,数组的最后收尾就比较简单了,END_IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions)完成最后的定义闭环,宏定义如下:
#define END_IMPL_ARM_2D_REGION_LIST(...) \
};
将END_IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions)展开后就简化为:
};
我们将前面几个宏的展开代码合并到一起,就看到了一个完整的素组定义:
enum {
s_tDirtyRegions_offset = 100,
};
static arm_2d_region_list_item_t s_tDirtyRegions[] = {
{
.ptNext = (arm_2d_region_list_item_t *) &(s_tDirtyRegions[1]),
.tRegion = { 0,},
},
{
.ptNext = NULL,
.tRegion = {
.tLocation = {
.iX = 0,
.iY = 0,
},
.tSize = {
.iWidth = 0,
.iHeight = 8,
},},
},
};
至此,我们利用几个宏,非常简单的定义出来了一个脏矩形数组,并且形成了一个单向链表,后面将会提供给场景使用。
在arm2d里面,充分的利用宏的技巧,简化了非常多的工作,并且用C语言的方式,实现了优雅和完美的OOPC的编程,后续章节我们会陆续解密,敬请关注!
原创文章,欢迎转载,请注明出处,未经书面允许,不得用于商业用途