awtk很多控件比如progress_circle效率低下的问题
在awtk中优化的很不好,虽然可以用,但是效率非常低下,比如一个progress_circle控件,粗暴的强制刷新该控件的长宽占用的矩形框,而非实际使用到的矩形框,这个控件一般用来指示进度,那么实际利用率仅仅为一小段弧形,并且每次更新的时候更加是弧形里面的一小段,但是awtk中每一帧更新的为该控件的长款的矩形,非常影响效率,在没有用该控件之前我可以刷新率达到125帧/秒,用了一个400*400的progress_circle控件之后,刷新率降到了恐怖的30帧,,可以学习一下touchgfx的circle控件,源码touhgfx里面都有,他就是会去获取一个实际用到的圆弧上的一小段,同样用400x400的控件,如果我实际上使用到的圆弧宽度为20的话,awtk直接每一帧率刷新400x400的区域,在touchgfx里面每一帧刷新的区域只有20x20不到,性能差了好几百倍.所以请求awtk能否考虑一下性能上的优化,而非一味地增加功能,毕竟awtk面向的是跨平台,性能还是首要的,
我这里可以附上touchgfx的算法,可以在touchgfx的源码里面找到
/**
- This file is part of the TouchGFX 4.16.1 distribution.
-
© Copyright (c) 2021 STMicroelectronics. - All rights reserved.
- This software component is licensed by ST under Ultimate Liberty license
- SLA0044, the "License"; You may not use this file except in compliance with
- the License. You may obtain a copy of the License at:
-
www.st.com/SLA0044
*/
#include <touchgfx/widgets/canvas/Circle.hpp>
namespace touchgfx
{
Circle::Circle()
: CanvasWidget(),
circleCenterX(0), circleCenterY(0), circleRadius(0),
circleArcAngleStart(CWRUtil::toQ5
void Circle::setPrecision(int precision) { if (precision < 1) { precision = 1; } if (precision > 120) { precision = 120; } circleArcIncrement = precision; }
int Circle::getPrecision() const { return circleArcIncrement; }
void Circle::setCapPrecision(int precision) { if (precision < 1) { precision = 1; } if (precision > 180) { precision = 180; } circleCapArcIncrement = precision; }
int Circle::getCapPrecision() const { return circleCapArcIncrement; }
bool Circle::drawCanvasWidget(const Rect& invalidatedArea) const { CWRUtil::Q5 arcStart = circleArcAngleStart; CWRUtil::Q5 arcEnd = circleArcAngleEnd;
CWRUtil::Q5 _360 = CWRUtil::toQ5<int>(360);
// Put start before end by swapping
if (arcStart > arcEnd)
{
CWRUtil::Q5 tmp = arcStart;
arcStart = arcEnd;
arcEnd = tmp;
}
if ((arcEnd - arcStart) >= _360)
{
// The entire circle has to be drawn
arcStart = CWRUtil::toQ5<int>(0);
arcEnd = _360;
}
if (circleLineWidth != 0)
{
// Check if invalidated area is completely inside the circle
int32_t x1 = int(CWRUtil::toQ5(invalidatedArea.x)); // Take the corners of the invalidated area
int32_t x2 = int(CWRUtil::toQ5(invalidatedArea.right()));
int32_t y1 = int(CWRUtil::toQ5(invalidatedArea.y));
int32_t y2 = int(CWRUtil::toQ5(invalidatedArea.bottom()));
int32_t dx1 = abs(int(circleCenterX) - x1); // Find distances between each corner and circle center
int32_t dx2 = abs(int(circleCenterX) - x2);
int32_t dy1 = abs(int(circleCenterY) - y1);
int32_t dy2 = abs(int(circleCenterY) - y2);
int32_t dx = CWRUtil::Q5(MAX(dx1, dx2)).to<int>() + 1; // Largest hor/vert distance (round up)
int32_t dy = CWRUtil::Q5(MAX(dy1, dy2)).to<int>() + 1;
int32_t dsqr = (dx * dx) + (dy * dy); // Pythagoras
// From https://www.mathopenref.com/polygonincircle.html
int32_t rmin = ((circleRadius - (circleLineWidth / 2)) * CWRUtil::cosine((circleArcIncrement + 1) / 2)).to<int>();
// Check if invalidatedArea is completely inside circle
if (dsqr < rmin * rmin)
{
return true;
}
}
Canvas canvas(this, invalidatedArea);
CWRUtil::Q5 radius = circleRadius;
CWRUtil::Q5 lineWidth = circleLineWidth;
if (circleLineWidth > circleRadius * 2)
{
lineWidth = (circleRadius + circleLineWidth / 2);
radius = lineWidth / 2;
}
CWRUtil::Q5 arc = arcStart;
CWRUtil::Q5 circleArcIncrementQ5 = CWRUtil::toQ5<int>(circleArcIncrement);
moveToAR2(canvas, arc, (radius * 2) + lineWidth);
CWRUtil::Q5 nextArc = CWRUtil::Q5(ROUNDUP((int)(arc + CWRUtil::toQ5<int>(1)), (int)circleArcIncrementQ5));
while (nextArc <= arcEnd)
{
arc = nextArc;
lineToAR2(canvas, arc, (radius * 2) + lineWidth);
nextArc = nextArc + circleArcIncrementQ5;
}
if (arc < arcEnd)
{
// "arc" is not updated. It is the last arc in steps of "circleArcIncrement"
lineToAR2(canvas, arcEnd, (radius * 2) + lineWidth);
}
if (lineWidth == CWRUtil::toQ5<int>(0))
{
// Draw a filled circle / pie / pacman
if (arcEnd - arcStart < _360)
{
// Not a complete circle, line to center
canvas.lineTo(circleCenterX, circleCenterY);
}
}
else
{
CWRUtil::Q5 circleCapArcIncrementQ5 = CWRUtil::toQ5<int>(circleCapArcIncrement);
CWRUtil::Q5 _180 = CWRUtil::toQ5<int>(180);
if (arcEnd - arcStart < _360)
{
// Draw the circle cap
CWRUtil::Q5 capX = circleCenterX + (radius * CWRUtil::sine(arcEnd));
CWRUtil::Q5 capY = circleCenterY - (radius * CWRUtil::cosine(arcEnd));
for (CWRUtil::Q5 capAngle = arcEnd + circleCapArcIncrementQ5; capAngle < arcEnd + _180; capAngle = capAngle + circleCapArcIncrementQ5)
{
lineToXYAR2(canvas, capX, capY, capAngle, lineWidth);
}
}
// Not a filled circle, draw the path on the inside of the circle
if (arc < arcEnd)
{
lineToAR2(canvas, arcEnd, (radius * 2) - lineWidth);
}
nextArc = arc;
while (nextArc >= arcStart)
{
arc = nextArc;
lineToAR2(canvas, arc, (radius * 2) - lineWidth);
nextArc = nextArc - circleArcIncrementQ5;
}
if (arc > arcStart)
{
lineToAR2(canvas, arcStart, (radius * 2) - lineWidth);
}
if (arcEnd - arcStart < _360)
{
// Draw the circle cap
CWRUtil::Q5 capX = circleCenterX + (radius * CWRUtil::sine(arcStart));
CWRUtil::Q5 capY = circleCenterY - (radius * CWRUtil::cosine(arcStart));
for (CWRUtil::Q5 capAngle = arcStart - _180 + circleCapArcIncrementQ5; capAngle < arcStart; capAngle = capAngle + circleCapArcIncrementQ5)
{
lineToXYAR2(canvas, capX, capY, capAngle, lineWidth);
}
}
}
return canvas.render();
}
Rect Circle::getMinimalRect() const { return getMinimalRect(circleArcAngleStart, circleArcAngleEnd); }
Rect Circle::getMinimalRect(int16_t arcStart, int16_t arcEnd) const
{
return getMinimalRect(CWRUtil::toQ5
Rect Circle::getMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd) const
{
CWRUtil::Q5 xMin = CWRUtil::toQ5
void Circle::updateArc(CWRUtil::Q5 setStartAngleQ5, CWRUtil::Q5 setEndAngleQ5) { CWRUtil::Q5 startAngleQ5 = setStartAngleQ5; CWRUtil::Q5 endAngleQ5 = setEndAngleQ5; if (circleArcAngleStart == startAngleQ5 && circleArcAngleEnd == endAngleQ5) { return; }
// Make sure old start < end
if (circleArcAngleStart > circleArcAngleEnd)
{
CWRUtil::Q5 tmp = circleArcAngleStart;
circleArcAngleStart = circleArcAngleEnd;
circleArcAngleEnd = tmp;
}
// Make sure new start < end
if (startAngleQ5 > endAngleQ5)
{
CWRUtil::Q5 tmp = startAngleQ5;
startAngleQ5 = endAngleQ5;
endAngleQ5 = tmp;
}
// Nice constant
const CWRUtil::Q5 _360 = CWRUtil::toQ5<int>(360);
// Get old circle range start in [0..360[
if (circleArcAngleStart >= _360)
{
int x = (circleArcAngleStart / _360).to<int>();
circleArcAngleStart = circleArcAngleStart - _360 * x;
circleArcAngleEnd = circleArcAngleEnd - _360 * x;
}
else if (circleArcAngleStart < 0)
{
int x = 1 + ((-circleArcAngleStart) / _360).to<int>();
circleArcAngleStart = circleArcAngleStart + _360 * x;
circleArcAngleEnd = circleArcAngleEnd + _360 * x;
}
// Detect full circle
if ((circleArcAngleEnd - circleArcAngleStart) > _360)
{
circleArcAngleEnd = circleArcAngleStart + _360;
}
// Get new circle range start in [0..360[
if (startAngleQ5 >= _360)
{
int x = (startAngleQ5 / _360).to<int>();
startAngleQ5 = startAngleQ5 - _360 * x;
endAngleQ5 = endAngleQ5 - _360 * x;
}
else if (startAngleQ5 < 0)
{
int x = 1 + (-startAngleQ5 / _360).to<int>();
startAngleQ5 = startAngleQ5 + _360 * x;
endAngleQ5 = endAngleQ5 + _360 * x;
}
// Detect full circle
if ((endAngleQ5 - startAngleQ5) >= _360)
{
// Align full new circle with old start.
// So old[90..270] -> new[0..360] becomes new[90..450] for smaller invalidated area
startAngleQ5 = circleArcAngleStart;
endAngleQ5 = startAngleQ5 + _360;
}
else if ((circleArcAngleEnd - circleArcAngleStart) >= _360)
{
// New circle is not full, but old is. Align old circle with new.
// So old[0..360] -> new[90..270] becomes old[90..450] for smaller invalidated area
circleArcAngleStart = startAngleQ5;
circleArcAngleEnd = circleArcAngleStart + _360;
}
// New start is after old end. Could be overlap
// if old[10..30]->new[350..380] becomes new[-10..20]
if (startAngleQ5 > circleArcAngleEnd && endAngleQ5 - _360 >= circleArcAngleStart)
{
startAngleQ5 = startAngleQ5 - _360;
endAngleQ5 = endAngleQ5 - _360;
}
// Same as above but for old instead of new
if (circleArcAngleStart > endAngleQ5 && circleArcAngleEnd - _360 >= startAngleQ5)
{
circleArcAngleStart = circleArcAngleStart - _360;
circleArcAngleEnd = circleArcAngleEnd - _360;
}
Rect r;
if (startAngleQ5 > circleArcAngleEnd || endAngleQ5 < circleArcAngleStart)
{
// Arcs do not overlap. Invalidate both arcs.
r = getMinimalRect(circleArcAngleStart, circleArcAngleEnd);
invalidateRect(r);
r = getMinimalRect(startAngleQ5, endAngleQ5);
invalidateRect(r);
}
else
{
// Arcs overlap. Invalidate both ends.
if (circleArcAngleStart != startAngleQ5)
{
r = getMinimalRectForUpdatedStartAngle(startAngleQ5);
invalidateRect(r);
}
if (circleArcAngleEnd != endAngleQ5)
{
r = getMinimalRectForUpdatedEndAngle(endAngleQ5);
invalidateRect(r);
}
}
circleArcAngleStart = setStartAngleQ5;
circleArcAngleEnd = setEndAngleQ5;
}
void Circle::moveToAR2(Canvas& canvas, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const { canvas.moveTo(circleCenterX + ((r2 * CWRUtil::sine(angle)) / 2), circleCenterY - ((r2 * CWRUtil::cosine(angle)) / 2)); }
void Circle::lineToAR2(Canvas& canvas, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const { lineToXYAR2(canvas, circleCenterX, circleCenterY, angle, r2); }
void Circle::lineToXYAR2(Canvas& canvas, const CWRUtil::Q5& x, const CWRUtil::Q5& y, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const { canvas.lineTo(x + ((r2 * CWRUtil::sine(angle)) / 2), y - ((r2 * CWRUtil::cosine(angle)) / 2)); }
void Circle::updateMinMaxAR(const CWRUtil::Q5& a, const CWRUtil::Q5& r2, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const { CWRUtil::Q5 xNew = circleCenterX + ((r2 * CWRUtil::sine(a)) / 2); CWRUtil::Q5 yNew = circleCenterY - ((r2 * CWRUtil::cosine(a)) / 2); updateMinMaxXY(xNew, yNew, xMin, xMax, yMin, yMax); }
void Circle::updateMinMaxXY(const CWRUtil::Q5& xNew, const CWRUtil::Q5& yNew, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const { if (xNew < xMin) { xMin = xNew; } if (xNew > xMax) { xMax = xNew; } if (yNew < yMin) { yMin = yNew; } if (yNew > yMax) { yMax = yNew; } }
void Circle::calculateMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const { // Put start before end by swapping if (arcStart > arcEnd) { CWRUtil::Q5 tmp = arcStart; arcStart = arcEnd; arcEnd = tmp; }
CWRUtil::Q5 _90 = CWRUtil::toQ5<int>(90);
CWRUtil::Q5 _360 = CWRUtil::toQ5<int>(360);
if ((arcEnd - arcStart) >= _360)
{
// The entire circle has to be drawn
arcStart = CWRUtil::toQ5<int>(0);
arcEnd = _360;
}
// Check start angle
updateMinMaxAR(arcStart, (circleRadius * 2) + circleLineWidth, xMin, xMax, yMin, yMax);
// Here we have a up to 4 approximation steps on angles divisible by 90
CWRUtil::Q5 i;
for (i = CWRUtil::Q5(ROUNDUP((int)(arcStart + CWRUtil::toQ5<int>(1)), (int)_90)); i <= arcEnd; i = i + _90)
{
updateMinMaxAR(i, (circleRadius * 2) + circleLineWidth, xMin, xMax, yMin, yMax);
}
// Check end angle
if ((i - _90) < arcEnd)
{
updateMinMaxAR(arcEnd, (circleRadius * 2) + circleLineWidth, xMin, xMax, yMin, yMax);
}
if (circleLineWidth == CWRUtil::toQ5<int>(0))
{
// A filled circle / pie / pacman
if ((arcEnd - arcStart) < _360)
{
// Not a complete circle, check center
updateMinMaxAR(CWRUtil::toQ5<int>(0), CWRUtil::toQ5<int>(0), xMin, xMax, yMin, yMax);
}
}
else
{
// Not a filled circle, check the inside of the circle. Only start and/or end can cause new min/max values
updateMinMaxAR(arcStart, (circleRadius * 2) - circleLineWidth, xMin, xMax, yMin, yMax);
updateMinMaxAR(arcEnd, (circleRadius * 2) - circleLineWidth, xMin, xMax, yMin, yMax);
}
// Check if circle cap extends the min/max further
if ((circleCapArcIncrement < 180) && (arcEnd - arcStart < _360))
{
// Round caps
CWRUtil::Q5 capX = circleCenterX + (circleRadius * CWRUtil::sine(arcStart));
CWRUtil::Q5 capY = circleCenterY - (circleRadius * CWRUtil::cosine(arcStart));
updateMinMaxXY(capX - (circleLineWidth / 2), capY - (circleLineWidth / 2), xMin, xMax, yMin, yMax);
updateMinMaxXY(capX + (circleLineWidth / 2), capY + (circleLineWidth / 2), xMin, xMax, yMin, yMax);
capX = circleCenterX + (circleRadius * CWRUtil::sine(arcEnd));
capY = circleCenterY - (circleRadius * CWRUtil::cosine(arcEnd));
updateMinMaxXY(capX - (circleLineWidth / 2), capY - (circleLineWidth / 2), xMin, xMax, yMin, yMax);
updateMinMaxXY(capX + (circleLineWidth / 2), capY + (circleLineWidth / 2), xMin, xMax, yMin, yMax);
}
}
Rect Circle::getMinimalRectForUpdatedStartAngle(const CWRUtil::Q5& startAngleQ5) const
{
CWRUtil::Q5 minAngle = CWRUtil::Q5(0); // Unused default value
CWRUtil::Q5 maxAngle = CWRUtil::Q5(0); // Unused default value
int circleArcIncrementQ5int = (int)CWRUtil::toQ5
Rect Circle::getMinimalRectForUpdatedEndAngle(const CWRUtil::Q5& endAngleQ5) const
{
CWRUtil::Q5 minAngle = CWRUtil::Q5(0); // Unused default value
CWRUtil::Q5 maxAngle = CWRUtil::Q5(0); // Unused default value
int circleArcIncrementQ5int = (int)CWRUtil::toQ5
你帮我用这个分支测试吧,谢谢:https://github.com/zlgopen/awtk/tree/opt_progress_circle
好的 愿意帮忙测试 希望awtk越来越好 这个是不是还跟脏矩形那边lcd.inc那个也需要优化。这个刷新区域是脏矩形那边获取判断的吧
你帮我用这个分支测试吧,谢谢:https://github.com/zlgopen/awtk/tree/opt_progress_circle
我测试了一下比之前好很多很多了,已经可以用在仪表上面60帧率了,但是还是觉得有优化的空间,比如400x400的控件,然后linewidth设置为100,如果我背景色为透明色,前景为图片,实际前景图片只有线宽10的有效区域,那么实际上percent的控件还是按照100线宽来计算脏矩形的,是否可以智能的去判断透明区域,把透明区域减掉,这样还可以继续优化比较大的空间,就是前景背景在处理透明的时候要把透明区域剪裁掉,当然我只是用printf来debug测试了一下,还没有用到jlink来具体测试,我会继续关注的,非常感谢大佬用心过年还在加班解决优化
好的,谢谢。
“linewidth设置为100,如果我背景色为透明色,前景为图片,实际前景图片只有线宽10的有效区域”有实际需求,还是只是方便一点?“智能的去判断透明区域”也是需要花时间的,我也不想把代码弄得太复杂。
好的,谢谢。
“linewidth设置为100,如果我背景色为透明色,前景为图片,实际前景图片只有线宽10的有效区域”有实际需求,还是只是方便一点?“智能的去判断透明区域”也是需要花时间的,我也不想把代码弄得太复杂。
实际上这个控件很重要的 经常用在仪表盘上面的 最最关键的一个控件 用来配合指针旋转背景用的 你们awtk那个配合rt1052还是imux6上面那个仪表demo没有用的这个功能 但是基本上所有的仪表控件都是需要这个功能的 这个控件拿来做百分比指示是大材小用了