Bonjour,
J'ai quasi terminé mon projet d'interface ethernet avec écran TFT tactile.
Le matériel utilisé est Arduino UNO + ILI9341 TFT + XPT2046 touch + Shield Ethernet II
Une fonction impacte fortement la vitesse d'affichage
Elle sert à faire des "dégradés" de couleur :
- les dégradés peuvent être hozirontaux ou verticaux (couleur fonction de X ou de Y)
- le dégradé peut être "normal" (couleur de départ -> couleur d'arrivée)
- le dégradé peut être "arc en ciel"
- il y a un dégradé "fixed middle", c'est un mode "3 couleurs" : couleur de départ si X < début, couleur d'arrivée si X > fin et couleur fixe à la place du dégradé
Cela peut sembler étrange mais c'est très pratique.
Par exemple vous voulez dessiner un graphique type "équaliseur HiFi", hé bien grâce au dégradé "fixed middle" vous dessinez les rectangles de votre équaliseur et ils auront automatiquement la zone verte, la zone jaune éventuelle et la zone rouge éventuelle comme il faut.
Les dégradés permettent des effets visuels intéressants qui compensent le fait qu'on ne puisse faire que des graphismes monochromes (à la base, une forme élémentaire n'a qu'une seule couleur) et sans antialiasing
Ainsi, en mettant un rectangle, un ou deux triangles ou un bitmap dessinés avec une couleur dégradée, on peut avoir des dessins ou des icônes donnant une impression de relief ou une impression de couleur comme sur un écran d'ordinateur.
Cela permet à ma petite interface qui a peu de ressources de proposer des graphismes dignes de l'an 2020
J'ai en grande partie résolu le problème de performance en appelant cette fonction le moins souvent possible.
En fonction du type de dégradé (horizontal ou vertical), je me débrouille pour dessiner les graphismes soit avec des segments de droite horizontaux ou soit avec des segments de droite verticaux.
Du coup la fonction est appelée un fois par ligne au lieu d'être appelée "bêtement" à chaque pixel.
C'est très efficace.
Je peux faire un dégradé qui remplit tout l'écran et ça ne demande que 7% de temps d'execution en plus qu'avec une couleur unique (186 millisecondes au lieu de 174)
Mais j'ai quand même quelques fonctions graphiques où ce principe ne peut pas être utilisé, et donc il faut calculer la couleur pour chaque pixel
J'ai aussi des fonctions graphiques qui ne dessinent que de petits segments de droite horizontaux ou verticaux, elles sont pénalisées.
Dans le cas le plus défavorable, typiquement une page de texte "opaque" avec couleur dégradé pour le texte et pour le fond, on peut dépasser une seconde pour l'affichage.
Rien de dramatique, si vous connaissez la librairie ILI9341 Adafruit vous trouverez certainement que ma version est déjà bien plus rapide !
J'ai fait un benchmark et la plupart des exemples sont rapides (moins de 200 ms) et la quasi totalité est sous les 400 ms.
J'aimerais bien arriver à optimiser encore un petit peu plus pour que tout soit rapide
Voici la fonction :
Je me demande si je ne devrais pas coder cette fonction en assembleur.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 // Variables globales uint8_t ColGradient_R; // R value when X or Y = ColGradient_XY_min uint8_t ColGradient_G; uint8_t ColGradient_B; int8_t ColGradient_DR; // R value when X or Y = ColGradient_XY_min + ColGradient_XY_delta is ColGradient_R + ColGradient_DR int8_t ColGradient_DG; int8_t ColGradient_DB; bool ColGradient_Mode; bool ColGradient_X_Direction; int ColGradient_XY_min; int ColGradient_XY_delta; bool ColGradient_Swap; int ColGradient_WH; // ... unsigned int ColGradient_Colour_Calc(int xy) { if (ColGradient_Swap) { xy = ColGradient_WH - xy; } else { xy -= ColGradient_XY_min; } int delta = ColGradient_XY_delta; if (xy >= delta) { return ColGradient2; } else if (xy <= 0) { return ColGradient1; } else { // ColGradient_Type // 00000000 Gradient // 01000000 Rainbow // 11000000 Rainbow Inv // 10000000 FixedMiddle if (ColGradient_Type == 0b10000000) { return Colour[Coul_Index]; } else { byte R; byte G; byte B; if (ColGradient_Type & 0b01000000) { if (ColGradient_Type & 0b10000000) xy = delta - xy; xy *= 4; if (xy < delta) { R = 31; G = 63 * xy / delta; B = 0; } else if (xy < 2 * delta) { R = 31 * (2 * delta - xy) / delta; G = 63; B = 0; } else if (xy < 3 * delta) { R = 0; G = 63 * (3 * delta - xy) / delta; B = 31 * (xy - 2 * delta) / delta; } else { R = 31 * (xy - 3 * delta) / delta; G = 0; B = 31; } } else { R = ColGradient_R; G = ColGradient_G; B = ColGradient_B; //if (ColGradient_DR !=0) R += ColGradient_DR * xy / delta; //if (ColGradient_DG !=0) G += ColGradient_DG * xy / delta; //if (ColGradient_DB !=0) B += ColGradient_DB * xy / delta; byte k = (xy << 6) / delta; if (ColGradient_DR !=0) R += (ColGradient_DR * k) >> 6; if (ColGradient_DG !=0) G += (ColGradient_DG * k) >> 6; if (ColGradient_DB !=0) B += (ColGradient_DB * k) >> 6; } return (R << 11) | (G << 5) | B; } } }
Ou au moins la partie la plus lente qui est celle-ci :
Dans mon projet j'ai déjà des bouts d'assembleur provenant des bibliothèques que j'ai modifié ; voici un example :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 byte R; byte G; byte B; if (ColGradient_Type & 0b01000000) { if (ColGradient_Type & 0b10000000) xy = delta - xy; xy *= 4; if (xy < delta) { R = 31; G = 63 * xy / delta; B = 0; } else if (xy < 2 * delta) { R = 31 * (2 * delta - xy) / delta; G = 63; B = 0; } else if (xy < 3 * delta) { R = 0; G = 63 * (3 * delta - xy) / delta; B = 31 * (xy - 2 * delta) / delta; } else { R = 31 * (xy - 3 * delta) / delta; G = 0; B = 31; } } else { R = ColGradient_R; G = ColGradient_G; B = ColGradient_B; //if (ColGradient_DR !=0) R += ColGradient_DR * xy / delta; //if (ColGradient_DG !=0) G += ColGradient_DG * xy / delta; //if (ColGradient_DB !=0) B += ColGradient_DB * xy / delta; byte k = (xy << 6) / delta; if (ColGradient_DR !=0) R += (ColGradient_DR * k) >> 6; if (ColGradient_DG !=0) G += (ColGradient_DG * k) >> 6; if (ColGradient_DB !=0) B += (ColGradient_DB * k) >> 6; } return (R << 11) | (G << 5) | B;
Qu'en pensez vous ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 // 17 cycle delay (including "call") static void delay17() __attribute__((noinline)) __attribute__((naked)) { __asm__ __volatile__ ( // +4 (call to get here) " adiw r24,0\n" // +2 (2-cycle NOP) " adiw r24,0\n" // +2 (2-cycle NOP) " adiw r24,0\n" // +2 (2-cycle NOP) " adiw r24,0\n" // +2 (2-cycle NOP) #if !defined(__AVR_HAVE_RAMPD__) " nop\n" // +1 (2-cycle NOP) #endif " ret\n" // +4 (or +5 on >64KB AVR with RAMPD reg) // = 17 cycles : : : ); } // SPI 16-bit write with minimal hand-tuned delay (assuming max DIV2 SPI rate) static INLINE void spiWrite16(uint16_t data) INLINE_OPT { uint8_t temp; __asm__ __volatile__ ( " out %[spi],%[hi]\n" // write SPI data (18 cycles until next write) " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) " out %[spi],%[lo]\n" // write SPI data (18 cycles until next write) " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) : [temp] "=d" (temp) : [spi] "i" (_SFR_IO_ADDR(SPDR)), [lo] "r" ((uint8_t)data), [hi] "r" ((uint8_t)(data>>8)) : ); }
Sans surprise, c'est les multiplications et surtout la division qui est coûteuse.
La ligne "byte k = (xy << 6) / delta" qui permet d'économiser jusque 3 divisions m'a fait gagné pas mal de temps (c'est une astuce un peu barbare qui tiens compte du fait que les couleurs sont sur 16 bits donc R5 G6 B5)
Comment faire, ou trouver des ressources ?
Même si ça ne marche pas et/ou si l'optimisation est faible, ce sera toujours une chose intéressante à essayer et à faire
A bientôt
Partager