[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__是什么?简单科普一下:

  1. 基本概念:__COUNTER__是 GNU 编译器(如 GCC)的一个非标准扩展特性,它代表一个整数值,初始值通常为0,每遇到一次该宏就会自动加1
  2. 使用场景:这个宏常被用来防止重复声明冲突,例如在定义多个模板实例或类时,可以通过结合 ## 运算符来生成唯一的名称,从而避免命名冲突
  3. 唯一标识符:COUNTER 可以与 ## 运算符配合使用,以构建唯一的标识符。例如,可以将任意一个标识符与 COUNTER
    合并,形成一个新的唯一标识符
  4. 注意事项:在使用 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的编程,后续章节我们会陆续解密,敬请关注!
原创文章,欢迎转载,请注明出处,未经书面允许,不得用于商业用途

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/11783.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【网页设计】CSS 高级技巧

目标 能够使用精灵图能够使用字体图标能够写出 CSS 三角能够写出常见的 CSS 用户界面样式能够说出常见的布局技巧 1. 精灵图 为什么需要精灵图?精灵图的使用精灵图课堂案例 1.1 为什么需要精灵图? 一个网页中往往会应用很多小的背景图像作为修饰&…

数据结构——二叉树和BST

树与二叉树 基本概念 树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接…

openGauss常见问题与故障处理(一)

大家好,欢迎大家收看本文。 对于初学者入门的学习,一些理论不容易理解或记住,所以本节课程【创新】采用了【正、反对比联想记忆】的方法, 引入模拟场景中的肖荏盖的小故事。(模拟场景为虚构演绎,仅供教学&…

计算机辅助几何设计:曲线曲面基础知识

参数化表示 空间曲线曲面常用参数化表示,即: x x ( u ) , y y ( u ) , z z ( u ) xx(u),yy(u),zz(u) xx(u),yy(u),zz(u)。用位置矢量形式表示就是 p p ( u ) pp(u) pp(u),其中参数u可能有意义,也可能没有意义,例如…

TF-Grasp论文学习笔记

当 Transformer 遇到机器人抓取时:利用上下文进行有效的抓取检测 摘要 在这篇论文中,我们提出了一个基于transformer结构的用于机器人抓取的网络,我们将其命名为TF-Grasp。TF-Grasp网络架构有两个重要的设计,这使其可以对于视觉抓…

剪绳子(math)-acwing

题目: AcWing 25. 剪绳子 - AcWing 代码 主要是处理末尾端几个2,其余都是3,这样相乘能最大,因为4可以分为2*2,3不能分,然后5也没有3*2大,6也没有3*3大。 总之2*2没有3*3大,所以6不…

Scrapy爬取heima论坛所有页面内容并保存到数据库中

前期准备: Scrapy入门_win10安装scrapy-CSDN博客 新建 Scrapy项目 scrapy startproject mySpider03 # 项目名为mySpider03 进入到spiders目录 cd mySpider03/mySpider03/spiders 创建爬虫 scrapy genspider heima bbs.itheima.com # 爬虫名为heima &#…

基于SpringBoot的垃圾分类回收系统+LW示例参考

1.项目介绍 系统角色:管理员、普通用户、回收员功能模块:管理员(用户管理、回收员管理、垃圾类型管理、商品分类管理、环保商城管理、上门回收管理、订单分配管理、订单管理、系统管理等)、回收员(订单分配、订单管理…

华为入围Linux 内核CVE 检视“五人团”,openEuler要再进阶?

背景:内核社区接管 Linux 社区漏洞发布 往年 Linux 内核漏洞发布存在来源不固定、覆盖不全面,有时发布无修复补丁的 CVE 从而形成 0-day 漏洞等问题,给 Linux 内核安全带来了不确定性,为了更规范化运作,2024 年 2 月 1…

JS爬虫实战之TikTok_Shop验证码

TikTok_Shop验证码逆向 逆向前准备思路1- 确认接口2- 参数确认3- 获取轨迹参数4- 构建请求5- 结果展示 结语 逆向前准备 首先我们得有TK Shop账号,否则是无法抓取到数据的。拥有账号后,我们直接进入登录。 TikTok Shop 登录页面 思路 逆向步骤一般分为…

同等学力申硕国考只考一门的专业有哪些?

同等学力申硕国考英语,英语不考听力,若进行考前有效辅导,英语单科通过率可以较大幅度提高。相对其他非全日制研究生和全日制研究生而言,考试科目少了,总分少,复习量也相对少,比较适合在职人员报…

烟火识别软件LiteAIServer视频智能分析平台支持烟雾检测算法

随着科技的不断发展,安防管理平台在企业和机构中的应用日益广泛。烟火识别软件LiteAIServer集成了视频监控、报警系统等多种安防功能,为用户提供了一站式解决方案。 烟雾检测是在安防已经落地的AI算法 ,主要应用于:厂区、森林、仓…

llamaIndex和langchain对比及优劣对比

一. LangChain vs LlamaIndex: 基本描述 LlamaIndex在搜索和检索任务方面表现出色。它是一个强大的数据索引和查询工具,非常适合需要高级搜索的项目。LlamaIndex能够处理大型数据集,从而实现快速准确的信息检索。 LangChain是一个模块化和灵活的工具集框…

免费体验OS和CAN配置|昂辉科技EasySAR Configurator demo推出

自2018年起,昂辉科技专注于汽车电子行业,深耕车载基础软件领域,已研发出符合AUTOSAR标准的EasySAR车载基础软件平台。该平台包含基础软件包和配置工具链,旨在赋能产业链与供应链,推动行业发展。 EasySAR配置工具支持…

Android 源码的下载与编译

Android 源码的下载与编译 本章节主要介绍安卓系统的编译以及编译产物,根据我自己的经验只总结个人觉得重要的部分。 有价值的博客: https://blog.csdn.net/wuye110/article/details/8463409 https://juejin.cn/post/7288166472131018786 值得一看的…

docker安装portainer

1、拉取镜像 docker pull portainer/portainer-ce:latest2、执行 docker run -d --restartalways --name portainer -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /data/portainer/data:/data -v /data/portainer/public:/public portainer/portain…

手写jdbc 工具类(封装思维,解决硬编码...)

目录 前言 手写jdbc 工具类 封装思维 对于封装思维的理解 举一个关于封装思维的例子 解决硬编码 什么是硬编码? 硬编码的例子: 解决办法 解法1 解法2 解法3 jdbc工具类总的代码如下 资源关闭的先后问题 解决办法: 总结 …

The First项目报告:抗 MEV 交易的CoW Protocol什么?

2023年,当UNIswap推出UniswapX 时,市场迎接它的不是赞叹,而是一片争议。UniswapX被指抄袭 CoWSwap 和 1inch。Curve 官方称 1inch 和 CoWSwap 早已改变游戏规则,UniswapX 非首创。CoWSwap 强调其 Intent Based Trading 的先驱地位…

微服务day06

MQ入门 同步处理业务: 异步处理: 将任务处理后交给MQ来进行分发处理。 MQ的相关知识 同步调用 同步调用的小结 异步调用 MQ技术选型 RabbitMQ 安装部署 其中包含几个概念: publisher:生产者,也就是发送消息的一方 …

C语言 | Leetcode C语言题解之第541题反转字符串II

题目&#xff1a; 题解&#xff1a; void swap(char* a, char* b) {char tmp *a;*a *b, *b tmp; }void reverse(char* l, char* r) {while (l < r) {swap(l, --r);} }int min(int a, int b) {return a < b ? a : b; }char* reverseStr(char* s, int k) {int n strl…