성능
이 섹션에서는 내장되는 그래픽 사용자 인터페이스의 성능에 대해 알아보겠습니다.
여기에서 정의하는 고성능이란 높은 프레임 속도를 유지하면서 원하는 그래픽 효과와 애니메이션을 구현할 수 있는 것을 말합니다.
이전 섹션에서 우리는 사용자 인터페이스의 프레임 속도가 메인 루프의 영향을 얼마나 받는지 살펴보았습니다. 병렬 RGB 디스플레이가 LTDC에 연결되어 있고, 프레임버퍼가 2개라고 다시 한 번 가정하겠습니다. 기본적인 상황은 아래와 같습니다.
디스플레이가 초당 60회 리프레시된다고 가정했을 때 각 리프레시 사이에 약 16ms의 시간이 존재합니다. The calculation is this:
1 s / 60 = 0.01667 s = 16.67 ms.
TouchGFX는 프레임버퍼1의 전송이 시작되는 시간에 프레임1을 프레임버퍼2로 그리기 시작합니다. 다음 전송이 시작되기 전에 프레임1 렌더링이 끝나면 프레임버퍼 2를 전송할 수 있습니다. 16.67ms 안에 렌더링이 끝나지 않으면 프레임버퍼 1이 다시 전송되고 디스플레이가 변함없이 그대로 표시됩니다.
이러한 상황을 프레임 손실이라고 합니다.
The time for the collect and update phases are typically minuscule, e.g. less than 1 ms, and therefore more or less negligible when considering the overall time taken of the main loop. 따라서 지금부터, 그리고 일반적으로 렌더링 시간을 고려할 때는 수집 단계와 업데이트 단계도 포함된 것입니다.
다수의 프레임에서 렌더링 시간이 제한 시간인 16.67ms를 초과할 경우 디스플레이의 프레임 속도는 초당 30프레임(fps)이 됩니다.
렌더링 시간이 대부분 16.67ms보다 짧더라도 일부 프레임에서 16.67ms보다 길면 평균 프레임 속도는 60fps에 가까워질 수 있습니다. 하지만 애니메이션은 사용자에게 자연스럽게 보이지 않을 수도 있습니다. 애플리케이션에 따라 일부 애니메이션 단계는 빠르고, 일부는 느리게 보일 수도 있습니다. This is not desirable.
또한 렌더링 시간이 더 길어질 수도 있습니다. If it is just above 33 ms, the frame rate will drop to 20 fps as we only have a new frame ready on every third transfer.
| FPS | 최대 렌더링 시간 |
|---|---|
| 60 | 16.67ms |
| 30 | 33.34ms |
| 20 | 50.00ms |
| 15 | 66.67ms |
The table shows the maximum rendering time (including the collect and update phases) that is available for a given frame rate.
사용자 인터페이스의 성능을 높이려면 프레임 속도를 주기적으로 확인하고 모니터링하는 것이 좋습니다. 이때 사용할 수 있는 방법은 두 가지입니다.
- 렌더링 시간 측정
- 손실된 프레임 계산
렌더링 시간 측정
첫째, 렌더링 시간 측정은 가장 상세한 정보를 얻을 수 있는 방법입니다. 이때는 기본적으로 프레임 전송부터 렌더링 단계가 끝날 때까지 걸리는 시간을 측정합니다. 수집 단계가 시작되면 그래픽 엔진이 GPIO 클래스 함수를 호출하고, 렌더링 단계가 끝나면 한 번 더 호출합니다. The application defines these functions and can hook into them to perform measurements.
측정은 다음과 같이 두 가지 방식으로 이루어집니다.
- Use an external timing device like an oscilloscope: To measure using an oscilloscope, the application should implement the
set(GPIO_ID)andclear(GPIO_ID)methods from theGPIOinterface. 그러면 오실로스코프가 출력이 높을 때도 시간 경과에 따른 렌더링 시간을 측정할 수 있습니다. - 내부 타이머 사용: sysTick과 같은 내부 타이머를 사용하는 방법이 있습니다. GPIO::set(RENDER_TIME)가 호출되면 애플리케이션이 타이머 값을 변수로 저장할 수 있습니다. 이후 clear가 호출되면 애플리케이션이 타이머를 다시 읽고 이전 값을 감산하여 렌더링 시간을 구합니다. 측정 해상도는 타이머의 속도에 따라 결정됩니다. 또한 애플리케이션은 어떻게든 렌더링 시간을 표시해야 합니다. 이때 한 가지 방법은 값을 전역 변수로 저장하여 스크린에서 TextArea에 표시하는 것입니다. 또한 디버거를 사용해 값을 확인하는 방법도 있습니다.
손실된 프레임 계산
그래픽 엔진은 마지막 수집-업데이트-렌더링 단계에서 수행된 전송 횟수를 계산합니다. 애플리케이션은 이 값을 확인하여 프레임 손실로 인해 프레임 속도가 떨어졌는지 여부를 쉽게 알 수 있습니다.
계산은 다음과 같이 HAL 클래스에서 가능합니다.
void handleTickEvent() {
tickCounter += 1;
if (HAL::getInstance()->getLCDRefreshCount() > 1) {
//Alert programmer somehow
...
}
}
손실된 프레임 보상
When frames are lost and the frame rate of one of our animations therefore lowered we can compensate to a certain degree. 방법은 다음 중 한 가지입니다.
- 끝까지 기다립니다 - 애니메이션을 계속 실행합니다. 애니메이션 시간이 길어질 뿐만 아니라 자주 끊길 수도 있습니다.
- skip some frames - make sure that the overall animation does not take more time than intended by skipping frames.
TouchGFX can be instructed to automatically skip some frames, when frames are lost. 그 방법은 실제 프레임마다 애니메이션을 한 번 이상 실행하면 됩니다. 이는 렌더링 시간이 불규칙할 때 애니메이션을 보다 자연스럽게 실행하는 데 효과적입니다.
HAL.hpp
void setFrameRateCompensation(bool enabled)
렌더링 시간에 영향을 미치는 요인
렌더링 시간에 영향을 미치는 요인은 업데이트되는 부분의 크기, 계층화 사용, 위젯의 복잡성, 렌더링 시 하드웨어 지원 등 다양합니다.
업데이트해야 하는 스크린의 영역
렌더링 시간은 일반적으로 업데이트할 픽셀의 수에 비례합니다. 애니메이션의 렌더링 시간이 너무 길면 애니메이션 영역을 줄이는 것이 좋습니다. 예를 들어 회전하는 이미지가 있는데 성능이 떨어진다면 이미지의 크기를 줄여서 성능을 개선할 수 있습니다.
단, 그래픽 엔진은 애플리케이션에서 무효화한 영역을 다시 그리기를 합니다. This means that it is important to only invalidate the areas that actually require a refresh.
무효화 영역이 커질수록 렌더링 시간도 길어집니다.
그래픽의 계층 수
일반 애플리케이션에서 그래픽은 여러 가지 요소가 서로 스택을 이루어 구성됩니다. 따라서 한 가지 요소 업데이트하면 일반적으로 모든 요소를 다시 그려야 합니다.
대표적으로 배경 이미지와 프레임, 그리고 일부 텍스트가 그렇습니다.
이 사용자 인터페이스는 투명 프레임이 보이는 이미지 위젯 위에 TextArea 위젯을 배치한 것입니다. 두 위젯 모두 배경 이미지 위에 있습니다.
This solution is used very often in applications. 또한 유연성이 높아서 매우 쉬운 해결책이기도 합니다. 예를 들어 런타임에서 프레임 변경이 가능할 뿐만 아니라 배경에서 프레임과 텍스트를 이동시키는 것도 가능합니다.
텍스트가 런타임에서 업데이트되어 다시 그려야 할 경우에는 그래픽 엔진도 배경과 프레임, 그리고 이어서 새로운 텍스트까지 다시 그려야 합니다. 이에 따라 텍스트를 렌더링하는 시간도 증가하기 때문에 렌더링 시간에 문제가 발생합니다.
무효화 영역에 계층이 많을수록 렌더링 시간도 길어집니다.
픽셀 렌더링의 복잡성
픽셀이라고 해서 프레임버퍼로 렌더링하는 것이 다 어려운 것은 아닙니다. 어떤 렌더링이 되었든지 그래픽 엔진이 해당하는 픽셀을 프레임버퍼에 작성해야 합니다. 하지만 작성할 픽셀을 계산하는 드는 손실은 각기 다릅니다.
예를 들어 Box Widget에 사용되는 고정된 색상은 가장 낮은 손실이 드는데, 그 이유는 픽셀을 한 번만 계산하면 모든 픽셀에 재사용할 수 있기 때문입니다. 즉, Box들을 많이 사용하여 매우 높은 성능을 얻는 셈입니다. 하지만 사용자 인터페이스의 품질이 높지 않을 때는 권장하지 않는 방법입니다.
이미지는 픽셀 계산에 드는 손실이 두 번째로 낮은데, 이는 픽셀이 언제든지 사용할 수 있는 형식으로 비트맵에 저장되기 때문입니다. 픽셀을 계산하여 프레임버퍼에 작성하려면 정확한 비트맵 위치에서 색상 값을 가져와야 합니다.
텍스트는 문자 하나마다 실제로 작은 이미지로 표현되기 때문에 손실이 이미지와 맞먹습니다. 실제로 손실이 더 높습니다. 작은 이미지가 너무 많아서 “시작-정지” 손실이 크게 늘어나기 때문입니다. 각 문자의 위치를 계산하는 경우에도 그렇습니다. 텍스트는 최대한 충실하게 보일 수 있도록 투명도를 적용하여 작은 이미지로 표현됩니다. 투명도에 대한 내용은 아래를 참조하십시오.
회전하거나 확대/축소된 이미지는 비용이 더 높습니다. 마찬가지로 비트맵에서 픽셀 값을 가져오지만 계산 시간이 더 오래 걸리는데, 그 이유는 그래픽 엔진이 확대/축소와 회전까지 고려해야 하기 때문입니다.
원형과 같은 기하학적 요소는 손실이 훨씬 더 높습니다. 이때는 비트맵에서 픽셀 색상을 가져오지 못해서 원형 형상과 원형의 각 픽셀 색상까지 모두 계산해야 하기 때문입니다.
투명도가 적용되면 요소를 그리는데 드는 손실이 증가합니다. 일부 픽셀이 불투명하지 않은 요소는 투명합니다. 이 경우 그래픽 엔진이 투명 요소 뒤에 있는 요소를 먼저 그려야 하기 때문에 “프레임 내 텍스트” 섹션에서도 보았듯이 그리는 손실이 증가하게 됩니다. 그런 다음 그래픽 엔진이 배경 픽셀과 투명 요소의 픽셀을 결합하여 그 결과를 프레임버퍼에 작성해야 합니다. 이러한 계산 과정은 단순히 이미 계산된 픽셀을 작성하는 것보다 훨씬 더 많은 시간이 소요됩니다.
투명 요소는 항상 계층이 하나 더 있습니다. 하지만 불투명 픽셀이 다른 불투명 픽셀 위에 겹친다고 해서 반드시 계층 수가 늘어나는 것은 아닙니다. The graphical engine tries not to draw pixels that are covered by other solid pixels, as this would be a waste of precious time.
무효화 영역에 손실이 높은 요소가 많을수록 렌더링 시간이 길어집니다.
Remember that it is only the elements that are part of the invalidated area that add to the rendering time. 무효화 영역 외부의 요소는 렌더링 시간에 영향을 미치지 않습니다.
UI 구성요소와 성능에 대한 자세한 내용은 여기에서 확인하십시오.
렌더링을 지원하는 하드웨어
Some STM32 microcontrollers contain graphical accelerators. These accelerators can reduce rendering time, as the accelerators can run in parallel with the microcontroller core. The core will then be able to run other tasks, while the accelerators render graphics.
The accelerators are automatically used by TouchGFX when available.
렌더링 시간을 고려해야 하는 경우
렌더링 시간이 항상 중요한 것은 아닙니다. 프레임 속도가 눈에 띄게 낮을 때 렌더링 시간에 신경 써야 합니다. 일반적으로 일부 스크린 영역에서 애니메이션(회전하는 아이콘 등)을 실행하거나, 혹은 스크린에서 임의 요소를 움직이거나 밀어서 움직일 때가 여기에 해당합니다. 또한 업데이트 주파수가 낮으면 사용자에게 자연스럽게 표시되지 않고 단계적으로 표시됩니다. 이러한 경우에도 렌더링 시간을 확인해야 합니다.
그 밖에 전체 스크린을 새로운 스크린으로 변경하는 경우에는 프레임 속도가 크게 떨어지더라도 보통은 사용자의 눈에 띄지 않습니다. 그 이유는 사용자가 렌더링 시작 시점이 아닌, 종료 시점에만 인지할 수 있기 때문입니다.
위의 두 가지 규칙은 예를 들어 움직이는 애니메이션 요소에서는 계층을 적게 사용하고 복잡한 요소와 여러 계층은 사용을 자제해야 한다는 것을 의미합니다. 다른 스크린 영역에서는 이렇게 해도 문제가 되지 않을 수 있습니다.
위 예를 보면 왼쪽에 아날로그 시계가 있습니다. 시계 바늘 3개가 작고 긴 이미지들을 따라 회전하면서 렌더링됩니다. 이때는 시계 바늘이 계속 움직이는 것이 아니기 때문에 일반적으로 문제가 없습니다. 하지만 스크린에서 시계의 위치를 옮긴다면 모든 프레임에서 다시 그리기를 하여 문제가 될 수 있습니다. 회전하는 이미지를 그릴려면 시간이 오래 걸리기 때문입니다.
오른쪽에 스크롤 리스트가 있습니다. The user can move this list of numbers up and down, so we need a high frame rate for the user interface to appear responsive. 따라서 이때는 스크롤 리스트 요소의 렌더링 시간을 고려하거나 스크롤 리스트의 크기를 줄여야 합니다.
콘텐츠를 무효화하여 성능 최적화
일반적으로 전체 위젯이 무효화되지만, 그래픽 엔진은 전체 위젯 대신에 위젯의 콘텐츠만 무효화할 수 있습니다. 무효화할 영역을 줄이면 렌더링 시간이 눈에 띄게 단축될 것입니다. 렌더링 시간의 단축은 다음과 같은 요소에 달려 있습니다.
- 전체 위젯의 크기와 비교해 위젯 콘텐츠로 겹치는 영역의 크기.
- 해당 위젯으로 일부 또는 전체가 겹치는 배경 위젯(들).
The following figures illustrate the concept of invalidating content, by using the TextArea widget as an example. 그림 1은 위젯의 전체 영역을 보여줍니다. 그림 2는 TextArea::invalidate()를 사용할 때 무효화된 영역을 보여줍니다. 그림 3은 TextArea::invalidateContent()를 사용할 때 무효화된 영역을 보여줍니다.
TextArea::invalidateContent() 사용 예제
위젯이 다른 위젯과 겹치는 경우, TextArea::invalidate()를 사용해 전체 TextArea가 무효화가 되면 이러한 다른 위젯들을 다시 그려야 합니다. 대신에 TextArea::invalidateContent()를 사용하면 위젯을 불필요하게 무효화하고 다시 그리는 위험을 최소화할 수 있습니다. 예를 들어 Circle, Gauge 같이 값비싼 위젯에서는 특히 그렇습니다.
아래 그림은 TextArea::invalidateContent()를 사용해 배경 위젯(이미지 - ST 로고)의 무효화를 방지하는 방법을 보여줍니다. TextArea::invalidate()를 사용했다면 배경 위젯이 무효화되어 다시 그리게 되었을 것입니다.
좋은 성능을 달성하는 팁
이 섹션을 마치면서 좋은 성능을 얻기 위한 팁을 간략히 알려드리겠습니다.
- 변화가 없는 요소를 다시 그리지 마십시오. 디스플레이에서 불필요한 요소를 무효화하는 실수를 저질러서는 안 됩니다. 이는 아무런 이점도 없이 성능만 떨어지게 되는 셈입니다.
- 품질과 속도의 균형점을 찾으십시오. 요소의 복잡성을 줄이면 성능이 개선될 수 있습니다. 대부분의 경우 복잡성과 성능의 균형을 유지하는 것이 관건입니다.
- Utilize hardware capabilities The capability of a microcontroller with hardware acceleration is often higher than a microcontroller without. Consider using a microcontroller with hardware acceleration.
- 계산된 그래픽을 이미지로 변경하십시오. 계산된 원은 원 이미지보다 성능이 느립니다. 일반적으로 이미지는 대부분의 정적 요소를 대신할 수 있습니다.
- 디스플레이의 리프레시 비율을 조정하십시오. 이 섹션의 서두에서 얘기했다시피 리프레시 비율은 렌더링 시간의 최대 한계입니다. 렌더링 시간이 리프레시 비율을 초과하면 프레임 속도가 떨어지게 됩니다. 렌더링 시간이 리프레시 비율을 약간 초과한다면 디스플레이의 리프레시 비율을 예를 들어 55Hz(18.2ms에 해당)로 낮춰 프레임 속도를 높게 유지하는 것이 좋습니다.
Framebuffer strategies
The Partial Framebuffer, Emulated Framebuffer, and Hybrid Double Buffer strategies render updated graphics in small slices. To keep rendering time low, reduce the use of widgets that are expensive to draw in multiple small areas, such as TextArea with automatic line wrapping, Circle, Video, and SVGs.
Widgets such as Box, image based Widgets, and TextureMapper have very little overhead in this rendering pattern.
For the Emulated framebuffer strategy it is important to have an even load. It is adviced to separate expensive elements.
그래픽 가속기
고성능을 달성하는 일반적인 방법은 그래픽 가속기가 탑재된 STM32 MCU를 사용하는 것입니다. The STM32 MCU family mainly uses two graphics accelerators: Chrom-ART and NeoChrom GPU. Chrom-ART is also called DMA2D, and NeoChrom GPU is also called GPU2D.
The Chrom-ART accelerator is capable of copying and blending images between the image storage and the frame buffer. NeoChrom GPU can do the same, but is also capable of texture mapping and vector graphics.
Chrom-ART comes in three versions. The first version of Chrom-ART is capable of copying and blending. The second version is also capable of converting YCbCr data to RGB. 이는 JPEG 디코딩 성능을 향상시킵니다. The third version is called Chrom-ART2 (DMA2D3) and is command-list based, supports more image formats, and is capable of down-scaling with nearest-neighbor.
Compared to Chrom-ART, NeoChrom GPU is capable of accelerating more graphical operations and has a richer feature set.
NeoChrom GPU comes in two versions: NeoChrom GPU and NeoChromVG GPU. NeoChromVG GPU has the same feature set as NeoChrom GPU, but also has full hardware acceleration of vector rendering.
| 그래픽 기능 | Chrom-ART | Chrom-ART2 | NeoChrom GPU | NeoChromVG GPU |
|---|---|---|---|---|
| 지원되는 형식(TouchGFX 사용 시) | ARGB8888, RGB888, RGB565, A8, A4, L8-888, L8-8888 | ARGB8888, RGB888, RGB565, A8, A4, A2, A1, L8-565, L8-888, L8-8888 | ARGB8888, RGB888, RGB565, A8, A4, A2, A1 | ARGB8888, RGB888, RGB565, A8, A4, A2, A1 |
| 명령어 목록 기반 | 아니요 | 예 | 예 | 예 |
| 그리기 | 직사각형 | 직사각형 | 직사각형, 픽셀, 선, 삼각형, 다각형(다중 샘플링 안티앨리어싱(MSAA) 적용) | 직사각형, 픽셀, 선, 삼각형, 다각형(다중 샘플링 안티앨리어싱(MSAA) 적용) |
| 블리팅 | 복사, 알파 블렌딩, 픽셀 형식 변환 | 복사, 알파 블렌딩, 픽셀 형식 변환 | 복사, 알파 블렌딩, 픽셀 형식 변환, 컬러 키잉 | 복사, 알파 블렌딩, 픽셀 형식 변환, 컬러 키잉 |
| 확대/축소 | 아니요 | Yes (down-scaling with nearest neighbor) | 예 | 예 |
| 텍스처 매핑 | 아니요 | 아니요 | 예 | 예 |
| 벡터 그래픽 | 아니요 | 아니요 | 아니요* | 예 |
* Vector Graphics are partially hardware-accelerated with NeoChrom GPU when using TouchGFX. Full hardware acceleration for Vector Graphics is available with NeoChromVG GPU.
이러한 기능을 사용할 수 있으면 많은 TouchGFX 위젯이 가속됩니다.
| 위젯 | Chrom-ART | Chrom-ART2 | NeoChrom GPU | NeoChromVG GPU |
|---|---|---|---|---|
| Box, BoxWithBorder | 예 | 예 | 예 | 예 |
| Image, AnimatedImage, TiledImage, SnapshotWidget | 예 | 예 | 예 | 예 |
| Button, ButtonWithIcon, ButtonWithLabel, ToggleButton | 예 | 예 | 예 | 예 |
| RadioButton, RepeatButton | 예 | 예 | 예 | 예 |
| PixelDataWidget | 예 | 예 | 예 | 예 |
| TextArea, TextAreaWithWildcard, Keyboard | 부분적으로 | 예 | 예 | 예 |
| ScalableImage | 아니요 | 부분적으로 | 예 | 예 |
| TextureMapper, AnimatedTextureMapper | 아니요 | 아니요 | 예 | 예 |
| Circle, Line, Graph | 아니요* | 아니요* | 아니요* | 아니요* |
| SVG | 아니요* | 아니요* | 아니요** | 예 |
* The drawing/blending of pixels to the framebuffer is done by Chrom-ART or NeoChrom GPU, but the shape calculations are done in software.
** SVG rendering is partially hardware accelerated with NeoChrom GPU and fully accelerated with NeoChromVG GPU.
The operations that are not hardware-accelerated are software-rendered (implying a higher CPU load, and lower performance). As the table above shows, NeoChrom GPU can accelerate widgets like ScalableImage and TextureMapper. 즉, 높은 성능을 유지하면서 위젯을 보다 활발하게 사용할 수 있습니다.
NeoChrom과 함께 사용하는 TouchGFX에 대해 여기에서 자세히 알아보십시오.










