Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
GUI
提高dc_client_draw_point的效率
发布于 2019-08-03 22:16:03 浏览:1349
订阅该版
* 本帖最后由 aozima 于 2019-8-10 09:51 编辑 * 如果您还在使用之前的rtgui0.41~rtgui1.0,可以看看这个帖子.这里不讨论新的柿饼UI,对于绘制图片/字体等之类的操作,最终调用的都是_draw_point()函数,它的效率至关重要. 这里分析client类型的dc,它是最慢的那一环,源代码如下: ```static void rtgui_dc_client_draw_point(struct rtgui_dc *self, int x, int y) { rtgui_rect_t rect; rtgui_widget_t *owner; if (self == RT_NULL) return; if (!rtgui_dc_get_visible(self)) return; /* get owner */ owner = RTGUI_CONTAINER_OF(self, struct rtgui_widget, dc_type); x = x + owner->extent.x1; y = y + owner->extent.y1; if (rtgui_region_contains_point(&(owner->clip), x, y, &rect) == RT_EOK) { /* draw this point */ hw_driver->ops->set_pixel(&(owner->gc.foreground), x, y); } }``` 各版本的源代码区别不大,都与上述类似.该函数每次都需要调用rtgui_region_contains_point(), 以判断点(x,y)是否位于可绘制clip之内,它的消耗是不可控的,与clip的复杂度成正比,造成 绘图效率下降,改造这个地方,不要每次都调该函数; 方法是充分利用&rect参数;改造代码如下: ```struct rtgui_dc { /* type of device context */ rt_uint32_t type; rtgui_rect_t draw_rect; //增加一个辅助绘图的rect /* dc engine */ const struct rtgui_dc_engine *engine; }; ``` 在rtgui_dc_client_create()中初始化draw_rect ```dc->draw_rect.x1 = 0;dc->draw_rect.y1 = 0; dc->draw_rect.x2 = 0; dc->draw_rect.y2 = 0; ///////////////////////////////////////////////////////////////////////////////// static void rtgui_dc_client_draw_point(struct rtgui_dc *self, int x, int y) { rtgui_rect_t rect; rtgui_widget_t *owner; if (self == RT_NULL) return; if (!rtgui_dc_get_visible(self)) return; /* get owner */ owner = RTGUI_CONTAINER_OF(self, struct rtgui_widget, dc_type); x = x + owner->extent.x1; y = y + owner->extent.y1; if(rtgui_rect_contains_point(&dc->draw_rect, x, y)) //先从辅助rect中查找 goto do_drawing; else if(rtgui_region_contains_point(&(owner->clip), x, y, &dc->draw_rect)==RT_EOK) goto do_drawing; else return; do_drawing: /* draw this point */ hw_driver->ops->set_pixel(&(owner->gc.foreground), x, y); }``` 总结,每次绘制点,先判断是否位于draw_rect内,第一次当然会失败,后面会再从region.rects中 判断,并取得找到的rect,它作为draw_rect使用,当下次再绘制点时,p(x+1,y)或p(x,y+1)非常高 的概率是在draw_rect之内的,这样就降低了调用rtgui_region_contains_point()的次数至 region_data.numRects*2+1次;
查看更多
15
个回答
默认排序
按发布时间排序
bernard
2019-08-09
这家伙很懒,什么也没写!
从最初的设计来说,draw_point肯定是效率最低的,因为它会查看剪切域中是否包含相应的点。 如果是其他的,例如draw_line,rect等,效率会更好些。
bernard
2019-08-03
这家伙很懒,什么也没写!
赞的! 目前是GUI Engine,可以向这个软件包提交您的PR
iamyhw
2019-08-06
这家伙很懒,什么也没写!
提交失败了,似乎是未拥有该组的提交权限
iamyhw
2019-08-06
这家伙很懒,什么也没写!
因为dc_client没有自己的结构,它使用是struct rtgui_dc, rtgui_rect_t draw_rect放入struct rtgui_dc中,感觉怪怪的,因为其他dc用不到draw_rect. 或者派生一个struct rtgui_dc_client专门处理dc_client更合理, 另,感觉dc_client和dc_hw本来应该是一个,它们的区别只在于clip是否平坦,都属于widget层面的, 拆成两种类型,使用起来也挺怪
iamyhw
2019-08-08
这家伙很懒,什么也没写!
[i=s] 本帖最后由 iamyhw 于 2019-8-8 22:08 编辑 [/i] 测试: 环境:vS2012, 测试屏幕大小:640x480,颜色深度:16bits,测试场景如下图所示: [attach]9987[/attach] 在一个容器控件中,放置一些干扰控件,使容器控件的clip变得复杂一点, 然后在容器控件的RTGUI_EVENT_PAINT事件中使用如下代码填充容器背景: int draw_point_cnt_track; //记录进入rtgui_region_contains_point()函数的次数 ``` draw_point_cnt_track = 0; tick = rt_tick_get(); //fill by draw_point for(y=rect.y1; y
draw_rect), x, y)) goto do_drawing; else if (rtgui_region_contains_point(&(owner->clip), x, y, &(self->draw_rect)) == RT_EOK) { draw_point_cnt_track++; goto do_drawing; } else return; do_drawing: /* draw this point */ hw_driver->ops->set_pixel(&color, x, y); ``` 测试结果如下,包含rtgui_region_dump()的结果: GUI simulator .... finsh>>clip: num_rects:115 extents: (0,0) (640,480) box[0]: (0,0) (640,10) box[1]: (0,10) (10,30) //...... box[113]: (630,450) (640,470) box[114]: (0,470) (640,480) cnt:1866, tick: 4 在每个box内平均调用rtgui_region_contains_point()的次数约16次多点, 而原先的代码,每绘制一个点,都要进入一次的,测试例程调用的次数为: cnt:155400,tick: 8 调用次数即像素数, 可以看出,调用rtgui_region_contains_point()的时间开销约4tick,也就是说它 占用了整个绘制过程的一半时间,整个绘制过程是调用许多个函数过程的, 所以,此处优化会带来较大的效率提升.========================================================= 补充:上面的方法不严谨,又重新设计了跟踪次数的计算模型,实际上,执行到最 后的else return;时,也调用rtgui_region_contains_point()了, 当,需要绘制的点不在clip的rects列表中时,这时候绘制点是没意义的,但是 都要扫一遍,因为只有扫完一遍之后才知道无效,这个时候的效率表现最差! 这也是dc_client效率低下的原因. 理论上,绘制全显的比半遮挡的效率高(例如窗口及其控件被遮挡),从上往下和 从左到右比乱序的效率高.
iamyhw
2019-08-08
这家伙很懒,什么也没写!
[i=s] 本帖最后由 iamyhw 于 2019-8-8 22:40 编辑 [/i] 继续理论方面的研究,看不懂别抱怨啊,理论毕竟不是人人都喜欢的. 前面分析了,从rtgui_region_contains_point()中取得一个返回的draw_rect, 作为首先判断的区域,以决定是否绘制这个点,这已然给效率带来可观的 提高.但是,它通常是理想情况,就像我上楼的测试例子,它是顺序的, 当部分控件被遮掩时,顺序的绘图,也会得到比较坏的效率,每次在draw_rect 中定位失败时,又需要扫一遍clip的rects.理想情形是根据draw_rect在rects中 的位置,决定向后或向前找新的draw_rect,这样应该可以更快命中吧, 后续测试测试.
bernard
2019-08-09
这家伙很懒,什么也没写!
肯定是不可能直接提交的,但这也是git的好处、魅力所在。你可以clone到自己的github仓库,然后修改、优化,然后再发起PR到原来的仓库。原仓库维护人review代码,给出建议修改或认可后,代码才会合并到主干中。
iamyhw
2019-08-09
这家伙很懒,什么也没写!
[i=s] 本帖最后由 iamyhw 于 2019-8-9 11:00 编辑 [/i] 感谢熊大的关注和指导,我先研究一下,并测试稳定了再PR更新. 其实,这个问题还真绕不开,毕竟界面很少有单一的,即使像Android之类的界面,没有浮动窗口,它的控件的clip是非常复杂的,可以从开发者模式下打开"绘制边界"功能看到. gui_engine绕不开也没办法绕开draw_point效率低下的问题,因为现有的image引擎和font引擎都是由rtgui_dc_draw_color_point()支持的,尤其作为背景,只要界面上多几个小控件,绘图速度就几何倍数的下降, 下面继续研究分析,我这样记下来,可以给我自己加深印象,也给别人一种直观的概念,如果只单单贴几行代码,也没人明白怎么回事,更遑论持续改进了. 前面分析到,首先获取一个有效的draw_rect,然后再绘制新点时,直接判断点是否在draw_rect内,以此来提高命中率,这个已经测试过是可行的,绘图效率已比原先提升了1倍.不过还是比dc_hw差很多.当然就像熊大说的,>draw_point肯定是效率最低的,因为它会查看剪切域中是否包含相应的点。 --- 一语惊醒梦中人,是的,问题就出在这里了。 正常clip只记录有效的绘图区域,并记录到clip的*rects中,所以当要绘制的点不在有效绘图区域时,程序至少要在rtgui_region_contains_point()函数内扫一遍才能知道它不在.这点上计算机太笨了.那么如果不让它扫有效的rects呢.如果有效的rect没命中,能找出个无效的rect来,起码不用白跑那么多趟了.(例如前面的例子跑1~115趟). 当然clip只记录有效rects列表,没有无效的rects列表.不过没关系.因为如果dc是client型的,说明它之上有控件,那它应该是一个容器,而container就有一个成员rtgui_list_t children;这些children的extent对于container的绘图来说,必然属于无效rects了. 根据这个思路再改改代码测测.
iamyhw
2019-08-09
这家伙很懒,什么也没写!
[i=s] 本帖最后由 aozima 于 2019-8-10 09:51 编辑 [/i] 使用新思路重新设计了点在clip.rects中的命中逻辑,增加了无效快速检测,代码如下:struct dc中增加 struct rtgui_rect draw_rect; /* 记录命中的有效rect */ struct rtgui_rect invalid_rect; /* 记录无效的rect */ 增加下面的函数,判断点是否属于容器下的某个子控件,如果是,则返回其extent,作为invalid_rect ``` static int _container_include_point(rtgui_container_t *container, int x, int y, rtgui_rect_t *rect) { rtgui_list_t* node; rtgui_list_foreach(node, &(container->children)) { rtgui_widget_t* w; w = rtgui_list_entry(node, rtgui_widget_t, sibling); if(INBOX(&(w->extent), x, y)) { *rect = w->extent; return RT_TRUE; } } return RT_FALSE; } ``` 改造rtgui_dc_client_draw_point和rtgui_dc_client_draw_color_point函数 ``` if(INBOX(&(dc->draw_rect), x, y)) { track_hit++; /* 跟踪命中次数 */ goto do_drawing; } else if(INBOX(&(dc->invalid_rect), x, y)) { track_cavity++; /* 跟踪空洞次数 */ return; } else if(rtgui_region_contains_point(&(owner->clip), x, y, &self->draw_rect)) { track_seek++; /* 跟踪扫有效rects次数 */ goto do_drawing; } else { track_fail++; /* 跟踪扫无效rects次数 */ if(RTGUI_IS_CONTAINER(owner)) _container_include_point(RTGUI_CONTAINER(owner), x, y, &(dc->invalid_rect)); return; } do_drawing: /* draw this point */ hw_driver->ops->set_pixel(&(owner->gc.foreground), x, y); ``` 测试结果,直接测试rtgui_image_blit函数的性能, [attach]10038[/attach] hit:153534, cavity:150290, seek:1866, fail:1510, tick:1 只判断有效命中的代码的测试结果: hit:153534, cavity:0, seek:1866, fail:151800, tick:4 原先的代码(每次都扫描clip的rects列表)的测试结果: hit:0, cavity:0, seek:155400, fail:151800, tick:9 使用最新思路设计的代码,时间复杂度几乎与剪切域的复杂度无关了, 其效率已逐渐逼近dc_hw,这个提升是非常显著的. 我再多测试其稳定性,比较新旧引擎代码的差异.
iamyhw
2019-08-09
这家伙很懒,什么也没写!
[i=s] 本帖最后由 iamyhw 于 2019-8-9 22:26 编辑 [/i] 回帖时,使用高级模式,代码部分会添加上
等之类的超文本标记,网站需要改善下
撰写答案
登录
注册新账号
关注者
0
被浏览
1.3k
关于作者
iamyhw
这家伙很懒,什么也没写!
提问
6
回答
128
被采纳
6
关注TA
发私信
相关问题
1
[已解决]创建GUI显示线程,出现bus fault错误应该怎么解?
2
移植RTGUI后,使用RealBoard4088的按键驱动切换画面,出现一次按键,触发两次事件的异常
3
【已解决】keil中添加rtgui的demo程序的问题
4
RT-GUI触摸BUG?
5
【已解决】请问:如何把example目录下rtgui的例子添加到工程中
6
RT-GUI不需要开辟一块和屏尺寸相同的存储空间记录屏幕上的数据吗?
7
RT-GUI开发疑问?
8
RTGUI 发送消息队列失败
9
gui的汉字字库能只挑出自己使用的汉字编译吗?
10
建议RTGUI的汉字和BMP等方框绘图增加窗口支持
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
freemodbus主机在freertos的适配,参考rtthread例程
2
开源共生 商业共赢 | RT-Thread 2024开发者大会议程正式发布!
3
【24嵌入式设计大赛】基于RT-Thread星火一号的智慧家居系统
4
RT-Thread EtherKit开源以太网硬件正式发布
5
还在担心bsp不好维护吗?快使用yml管理主线bsp
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
12
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
用户名由3_15位
14
个答案
2
次被采纳
rv666
9
个答案
2
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
8
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部