Nous comprenons les fonctionnalités du sous-système graphique des microcontrôleurs

salut!



Dans cet article, je voudrais parler des fonctionnalités de la mise en œuvre d'une interface utilisateur graphique avec des widgets sur un microcontrôleur et comment avoir à la fois une interface utilisateur familière et un FPS décent. Attention, je ne voudrais pas me concentrer sur une bibliothèque graphique spécifique, mais sur des choses générales - mémoire, cache du processeur, dma, etc. Puisque je suis développeur de l'équipe Embox , les exemples et les expériences seront sur ce RT OS.





Plus tôt, nous avons déjà parlé de l' exécution de la bibliothèque Qt sur un microcontrôleur . L'animation s'est avérée assez fluide, mais en même temps, les coûts de mémoire, même pour le stockage du micrologiciel, étaient importants - le code a été exécuté à partir de la mémoire flash externe QSPI. Bien sûr, lorsqu'une interface complexe et multifonctionnelle est requise, qui sait aussi faire une sorte d'animation, alors le coût des ressources matérielles peut être tout à fait justifié (surtout si vous avez déjà ce code développé pour Qt).



Mais que faire si vous n'avez pas besoin de toutes les fonctionnalités de Qt? Et si vous avez quatre boutons, un contrôle du volume et quelques menus contextuels? En même temps, je veux qu'il «soit beau et qu'il fonctionne vite» :) Ensuite, il sera conseillé d'utiliser des outils plus légers, par exemple la bibliothèque lvgl ou similaire.



Dans notre projet Embox, Nuklear a été porté il y a quelque temps - un projet pour créer une bibliothèque très légère composée d'un en-tête et vous permettant de créer facilement une interface graphique simple. Nous avons décidé de l'utiliser pour créer une petite application dans laquelle il y aura un widget avec un ensemble d'éléments graphiques et qui pourrait être contrôlé via un écran tactile.



STM32F7-Discovery avec Cortex-M7 et écran tactile a été choisi comme plate-forme.



Premières optimisations. Sauvegarder la mémoire



Ainsi, la bibliothèque graphique est sélectionnée, tout comme la plate-forme. Voyons maintenant quelles sont les ressources. Il est à noter ici que la mémoire principale SRAM est plusieurs fois plus rapide que la SDRAM externe, donc si les tailles d'écran vous le permettent, alors bien sûr, il est préférable de mettre le framebuffer dans SRAM. Notre écran a une résolution de 480x272. Si nous voulons une couleur de 4 octets par pixel, alors nous obtenons environ 512 Ko. Dans le même temps, la taille de la RAM interne n'est que de 320 et il est immédiatement clair que la mémoire vidéo sera externe. Une autre option consiste à réduire la profondeur de couleur en bits à 16 (soit 2 octets), et ainsi réduire la consommation de mémoire à 256 Ko, qui peut déjà tenir dans la RAM principale.



La première chose que vous pouvez essayer est d'économiser sur tout. Créons un tampon vidéo de 256 Kb, placez-le dans la RAM et dessinez-y. Le problème que nous avons immédiatement rencontré était le «scintillement» de la scène qui se produit lors du dessin directement dans la mémoire vidéo. Nuklear redessine la scène entière à partir de zéro, donc chaque fois que tout l'écran est rempli en premier, le widget est dessiné, puis un bouton y est placé, dans lequel le texte est placé, et ainsi de suite. En conséquence, à l'œil nu, on voit comment toute la scène est redessinée et l'image «clignote». Autrement dit, un simple placement dans la mémoire interne ne sauve pas.



Tampon intermédiaire. Optimisations du compilateur. FPU



Après avoir manipulé un peu la méthode précédente (placement dans la mémoire interne), des souvenirs de X Server et de Wayland ont immédiatement commencé à nous venir à l'esprit. Oui, en effet, en fait, les gestionnaires de fenêtres sont engagés dans le traitement des demandes des clients (juste notre application personnalisée), puis dans la collecte des éléments dans la scène finale. Par exemple, le noyau Linux envoie des événements des périphériques d'entrée au serveur via le pilote evdev. Le serveur, à son tour, détermine quel client doit traiter l'événement. Les clients, ayant reçu un événement (par exemple, en appuyant sur un écran tactile), exécutent leur logique interne - ils mettent en surbrillance le bouton, affichent un nouveau menu. De plus (légèrement différemment pour X et Wayland), soit le client lui-même, soit le serveur dessine les modifications dans le tampon. Et puis le compositeur rassemble toutes les pièces pour les dessiner à l'écran.Explication assez simple et schématique iciici .



Il est devenu clair que nous avons besoin d'une logique similaire, mais nous ne voulons vraiment pas pousser X Server dans stm32 pour une petite application. Par conséquent, essayons de dessiner non pas dans la mémoire vidéo, mais dans la mémoire ordinaire. Après avoir rendu la scène entière, il copiera le tampon dans la mémoire vidéo.



Code du widget
        if (nk_begin(&rawfb->ctx, "Demo", nk_rect(50, 50, 200, 200),
            NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|
            NK_WINDOW_CLOSABLE|NK_WINDOW_MINIMIZABLE|NK_WINDOW_TITLE)) {
            enum {EASY, HARD};
            static int op = EASY;
            static int property = 20;
            static float value = 0.6f;

            if (mouse->type == INPUT_DEV_TOUCHSCREEN) {
                /* Do not show cursor when using touchscreen */
                nk_style_hide_cursor(&rawfb->ctx);
            }

            nk_layout_row_static(&rawfb->ctx, 30, 80, 1);
            if (nk_button_label(&rawfb->ctx, "button"))
                fprintf(stdout, "button pressed\n");
            nk_layout_row_dynamic(&rawfb->ctx, 30, 2);
            if (nk_option_label(&rawfb->ctx, "easy", op == EASY)) op = EASY;
            if (nk_option_label(&rawfb->ctx, "hard", op == HARD)) op = HARD;
            nk_layout_row_dynamic(&rawfb->ctx, 25, 1);
            nk_property_int(&rawfb->ctx, "Compression:", 0, &property, 100, 10, 1);

            nk_layout_row_begin(&rawfb->ctx, NK_STATIC, 30, 2);
            {
                nk_layout_row_push(&rawfb->ctx, 50);
                nk_label(&rawfb->ctx, "Volume:", NK_TEXT_LEFT);
                nk_layout_row_push(&rawfb->ctx, 110);
                nk_slider_float(&rawfb->ctx, 0, &value, 1.0f, 0.1f);
            }
            nk_layout_row_end(&rawfb->ctx);
        }
        nk_end(&rawfb->ctx);
        if (nk_window_is_closed(&rawfb->ctx, "Demo")) break;

        /* Draw framebuffer */
        nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);

        memcpy(fb_info->screen_base, fb_buf, width * height * bpp);




Cet exemple crée une fenêtre de 200 x 200 px et y dessine des graphiques. La scène finale elle-même est dessinée dans le tampon fb_buf, que nous avons alloué à la SDRAM. Et puis dans la dernière ligne, memcpy est simplement appelé. Et tout se répète dans un cycle sans fin.



Si nous construisons et exécutons simplement cet exemple, nous obtenons environ 10 à 15 FPS. Ce qui n'est certainement pas très bon, car cela se remarque même à l'œil. De plus, comme le code de rendu de Nuklear contient beaucoup de calculs en virgule flottante, nous avons activé son support au départ , sans cela, le FPS aurait été encore plus bas. La première et la plus simple optimisation (gratuite) est bien sûr l'indicateur de compilateur -O2.



Construisons et exécutons le même exemple - nous obtenons 20 FPS. Mieux, mais toujours pas suffisant pour un bon travail.



Activation des caches de processeur. Mode d'écriture



Avant de passer à d'autres optimisations, je dirai que nous utilisons le plugin rawfb dans le cadre de Nuklear, qui dessine directement dans la mémoire. En conséquence, l'optimisation de la mémoire semble très prometteuse. La première chose qui me vient à l'esprit est le cache.



Dans les anciennes versions de Cortex-M, telles que Cortex-M7 (notre cas), un cache processeur supplémentaire (cache d'instructions et cache de données) est intégré. Il est activé via le registre CCR du bloc de commande système. Mais avec l'inclusion du cache, de nouveaux problèmes surviennent - l'incohérence des données dans le cache et la mémoire. Il existe plusieurs façons de gérer le cache, mais dans cet article, je ne m'étendrai pas sur elles, je vais donc passer à l'une des plus simples, à mon avis. Pour résoudre le problème d'incohérence du cache / mémoire, vous pouvez simplement marquer toute la mémoire disponible comme «non-cache». Cela signifie que toutes les écritures dans cette mémoire iront toujours en mémoire et non dans le cache. Mais si nous marquons toute la mémoire de cette manière, le cache ne servira à rien non plus. Il y a une autre option. Il s'agit d'un mode «pass-through», dans lequel toutes les écritures en mémoire marquées comme écriture immédiate sont simultanément envoyées vers le cache,et en mémoire. Cela crée une surcharge d'écriture, mais d'un autre côté, accélère considérablement la lecture, de sorte que le résultat dépendra de l'application spécifique.



Pour Nuklear, le mode d'écriture directe s'est avéré très bon - les performances sont passées de 20 FPS à 45 FPS, ce qui en soi est déjà assez bon et fluide. L'effet est certainement intéressant, nous avons même essayé de désactiver le mode d'écriture, sans prêter attention à l'incohérence des données, mais le FPS n'est passé qu'à 50 FPS, c'est-à-dire qu'il n'y avait pas d'augmentation significative par rapport à l'écriture. De cela, nous avons conclu que notre application nécessite beaucoup d'opérations de lecture, pas d'écritures. La question est, bien sûr, où? Peut-être en raison du nombre de transformations dans le code rawfb, qui accèdent souvent à la mémoire pour lire le coefficient suivant ou quelque chose comme ça.



Double tampon (jusqu'ici avec un tampon intermédiaire). Activer DMA



Je ne voulais pas m'arrêter à 45 FPS, alors nous avons décidé d'expérimenter davantage. L'idée suivante était le double tampon. L'idée est largement connue et, en général, simple. Nous dessinons la scène en utilisant un périphérique vers un tampon, tandis que l'autre périphérique affiche à partir d'un autre tampon. Si vous regardez le code précédent, vous pouvez clairement voir une boucle dans laquelle la scène est d'abord dessinée dans la mémoire tampon, puis le contenu est copié dans la mémoire vidéo à l'aide de memcpy. Il est clair que memcpy utilise le processeur, c'est-à-dire que le rendu et la copie se produisent de manière séquentielle. Notre idée était que la copie pouvait être effectuée en parallèle à l'aide de DMA. En d'autres termes, pendant que le processeur dessine une nouvelle scène, le DMA copie la scène précédente dans la mémoire vidéo.



Memcpy est remplacé par le code suivant:



            while (dma_in_progress()) {
            }

            ret = dma_transfer((uint32_t) fb_info->screen_base,
                    (uint32_t) fb_buf[fb_buf_idx], (width * height * bpp) / 4);
            if (ret < 0) {
                printf("DMA transfer failed\n");
            }

            fb_buf_idx = (fb_buf_idx + 1) % 2;


Ici, fb_buf_idx est entré - l'index du tampon. fb_buf_idx = 0 est le tampon avant, fb_buf_idx = 1 est le tampon arrière. La fonction dma_transfer () prend la destination, la source et un certain nombre de mots de 32 bits. Ensuite, le DMA est chargé des données requises et le travail se poursuit avec le tampon suivant.



Après avoir essayé ce mécanisme, les performances ont augmenté à environ 48 FPS. Un peu mieux que memcpy (), mais seulement légèrement. Je ne veux pas dire que le DMA s’est avéré inutile, mais dans cet exemple particulier, l’impact du cache sur la situation dans son ensemble s’est mieux montré.



Après une petite surprise que le DMA se comporte moins bien que prévu, nous avons eu l'idée «excellente», comme il nous semblait alors, d'utiliser plusieurs canaux DMA. À quoi ça sert? Le nombre de données pouvant être chargées simultanément dans DMA sur stm32f7xx est de 256 Ko. Dans le même temps, rappelez-vous que notre écran mesure 480x272 et que la mémoire vidéo est d'environ 512 Ko, ce qui signifie qu'il semblerait que vous puissiez placer la première moitié des données dans un canal DMA et la seconde moitié dans le second. Et tout semble être bon ... Mais les performances passent de 48 FPS à 25-30 FPS. Autrement dit, nous revenons à la situation où le cache n'a pas encore été activé. Avec quoi il peut être connecté? En fait, en raison du fait que l'accès à la mémoire SDRAM est synchronisé, même la mémoire est appelée mémoire d'accès aléatoire dynamique synchrone (SDRAM), donc cette option n'ajoute qu'une synchronisation supplémentaire,sans rendre parallèle l'écriture dans la mémoire, comme souhaité. Après un peu de réflexion, nous nous sommes rendu compte qu'il n'y a rien de surprenant ici, car il n'y a qu'une seule mémoire, et les cycles d'écriture et de lecture sont générés pour un microcircuit (sur un bus), et comme une autre source / récepteur est ajoutée, alors l'arbitre, qui résout les appels sur le bus , vous devez mélanger les cycles de commande de différents canaux DMA.



Double tampon. Travailler avec LTDC



La copie à partir d'un tampon intermédiaire est certainement une bonne chose, mais comme nous l'avons découvert, ce n'est pas suffisant. Jetons un coup d'œil à une autre amélioration évidente: la double mise en mémoire tampon. Dans la grande majorité des contrôleurs d'affichage modernes, vous pouvez définir l'adresse de la mémoire vidéo utilisée. Ainsi, vous pouvez éviter complètement de copier, et simplement réorganiser l'adresse de la mémoire vidéo dans la mémoire tampon préparée, et le contrôleur d'écran prendra les données de la manière optimale pour lui-même via DMA. Il s'agit d'un véritable double tampon, sans tampon intermédiaire comme auparavant. Il existe également une option lorsque le contrôleur d'affichage peut avoir deux tampons ou plus, ce qui est essentiellement la même chose - nous écrivons dans un tampon et l'autre est utilisé par le contrôleur, alors que la copie n'est pas nécessaire.



Le LTDC (contrôleur d'affichage LCD-TFT) dans le stm32f74xx a deux niveaux de superposition matérielle - couche 1 et couche 2, où la couche 2 est superposée sur la couche 1. Chacune des couches est configurable indépendamment et peut être activée ou désactivée séparément. Nous avons essayé d'activer uniquement la couche 1 et de réorganiser l'adresse de la mémoire vidéo sur le tampon avant ou le tampon arrière. Autrement dit, nous en donnons un à l'affichage et dans l'autre, nous dessinons à ce moment. Mais nous avons eu une gigue notable lors du changement de superposition.



Nous avons essayé l'option lorsque nous utilisons les deux couches avec l'une d'elles activée / désactivée, c'est-à-dire lorsque chaque couche a sa propre adresse de mémoire vidéo, qui ne change pas, et que le tampon est changé en activant l'une des couches tout en désactivant l'autre. La variation a également entraîné une gigue. Et enfin, nous avons essayé l'option lorsque le calque n'était pas désactivé, mais que le canal alpha était défini sur zéro 0 ou sur le maximum (255), c'est-à-dire que nous avons contrôlé la transparence, rendant l'un des calques invisible. Mais cette option n'a pas été à la hauteur des attentes, le tremblement était toujours présent.



La raison n'était pas claire - la documentation indique que les mises à jour de l'état des couches peuvent être effectuées à la volée. Nous avons fait un test simple - nous avons désactivé les caches, en virgule flottante, avons dessiné une image statique avec un carré vert au centre de l'écran, le même pour la couche 1 et la couche 2, et avons commencé à changer de niveau en boucle, dans l'espoir d'obtenir une image statique. Mais nous avons encore eu le même shake.



Il est devenu clair que c'était autre chose. Et puis nous nous sommes souvenus de l'alignement de l'adresse du framebuffer en mémoire. Comme les tampons ont été alloués à partir du tas et que leurs adresses n'étaient pas alignées, nous avons aligné leurs adresses de 1 Ko - nous avons obtenu l'image attendue sans gigue. Ensuite, ils ont découvert dans la documentation que LTDC soustrait les données par lots de 64 octets, et que l'inégalité des données entraîne une perte de performances significative. Dans ce cas, l'adresse du début du framebuffer et sa largeur doivent être alignées. Pour tester, nous avons changé la largeur de 480x4 en 470x4, qui n'est pas divisible par 64 octets, et avons obtenu la même gigue.



En conséquence, nous avons aligné les deux tampons de 64 octets, nous nous sommes assurés que la largeur était également alignée sur 64 octets et avons exécuté nuklear - la gigue a disparu. La solution qui a fonctionné ressemble à ceci. Au lieu de basculer entre les couches en désactivant complètement la couche 1 ou la couche, utilisez la transparence. Autrement dit, pour désactiver le niveau, définissez sa transparence sur 0 et pour l'activer - sur 255.



        BSP_LCD_SetTransparency_NoReload(fb_buf_idx, 0xff);

        fb_buf_idx = (fb_buf_idx + 1) % 2;

        BSP_LCD_SetTransparency(fb_buf_idx, 0x00);


Nous avons 70-75 FPS! Bien mieux que l'original 15.



Il convient de noter que la solution fonctionne grâce au contrôle de la transparence, et les options de désactivation de l'un des niveaux et l'option de réorganisation de l'adresse de niveau donnent une gigue d'image à FPS grand 40-50, la raison nous est actuellement inconnue. Aussi, en allant de l'avant, je dirai que c'est une solution pour cette carte.



Remplissage matériel de scène via DMA2D



Mais ce n'est pas la limite, notre dernière optimisation pour augmenter le FPS est le remplissage matériel de la scène. Avant cela, nous faisions le remplissage par programme:

nk_rawfb_render(rawfb, nk_rgb(30,30,30), 1);


Disons maintenant au plugin rawfb qu'il n'est pas nécessaire de remplir la scène, mais seulement de peindre:

nk_rawfb_render(rawfb, nk_rgb(30,30,30), 0);


Nous remplirons la scène de la même couleur 0xff303030, uniquement en matériel via le contrôleur DMA2D. L'une des principales fonctions de DMA2D est de copier ou de remplir un rectangle en RAM. La principale commodité ici est qu'il ne s'agit pas d'une mémoire continue, mais d'une zone rectangulaire, qui est située en mémoire avec des pauses, ce qui signifie que le DMA ordinaire ne peut pas être effectué tout de suite. Dans Embox, nous n'avons pas encore travaillé avec cet appareil, alors utilisons simplement les outils STM32Cube - la fonction BSP_LCD_Clear (uint32_t Color). Il programme la couleur de remplissage et la taille de l'écran entier dans DMA2D.



Période de suppression verticale (VBLANK)



Mais même à 80 FPS, un problème notable subsistait - des parties du widget se déplaçaient avec de petites «pauses» lors du déplacement sur l'écran. Autrement dit, le widget semblait être divisé en 3 parties (ou plus) qui se déplaçaient côte à côte, mais avec un léger retard. Il s'est avéré que la raison était une mise à jour incorrecte de la mémoire vidéo. Plus précisément, mises à jour aux mauvais intervalles de temps.



Le contrôleur d'affichage a une propriété telle que VBLANK, aka VBI ou Vertical Blanking Period . Il indique l'intervalle de temps entre les images vidéo adjacentes. Ou plus précisément, le temps entre la dernière ligne de l'image vidéo précédente et la première ligne de la suivante. Dans cet intervalle, aucune nouvelle donnée n'est transférée à l'écran, l'image est statique. Pour cette raison, il est sûr de mettre à jour la mémoire vidéo dans VBLANK.



En pratique, le contrôleur LTDC a une interruption qui est configurée pour être déclenchée après le traitement de la ligne de tampon de trame suivante (registre de configuration de position d'interruption de ligne LTDC (LTDC_LIPCR)). Ainsi, si vous configurez cette interruption sur le dernier numéro de ligne, nous n'obtiendrons que le début de l'intervalle VBLANK. À ce stade, nous effectuons la commutation de tampon nécessaire.



À la suite de telles actions, l'image est revenue à la normale, les lacunes ont disparu. Mais en même temps, le FPS est passé de 80 à 60. Comprenons quelle pourrait être la raison de ce comportement.



La formule suivante se trouve dans la documentation :



          LCD_CLK (MHz) = total_screen_size * refresh_rate,


où total_screen_size = total_width x total_height. LCD_CLK est la fréquence à laquelle le contrôleur d'affichage chargera les pixels de la mémoire vidéo sur l'écran (par exemple, via l'interface série d'affichage (DSI)). Mais refresh_rate est déjà le taux de rafraîchissement de l'écran lui-même, sa caractéristique physique. Il s'avère que, connaissant le taux de rafraîchissement de l'écran et ses dimensions, vous pouvez configurer la fréquence du contrôleur d'affichage. Après avoir vérifié les registres pour la configuration créée par le STM32Cube, nous avons découvert qu'il accordait le contrôleur à un écran de 60 Hz. Donc, tout s'est réuni.



Un peu sur les périphériques d'entrée dans notre exemple



Revenons à notre application et regardons comment fonctionne l'écran tactile, car comme vous le comprenez, l'interface moderne implique une interactivité, c'est-à-dire une interaction avec l'utilisateur.



Tout est arrangé tout simplement ici. Les événements des périphériques d'entrée sont traités dans la boucle principale du programme juste avant le rendu de la scène:



        /* Input */
        nk_input_begin(&rawfb->ctx);
        {
            switch (mouse->type) {
            case INPUT_DEV_MOUSE:
                handle_mouse(mouse, fb_info, rawfb);
                break;
            case INPUT_DEV_TOUCHSCREEN:
                handle_touchscreen(mouse, fb_info, rawfb);
                break;
            default:
                /* Unreachable */
                break;
            }
        }
        nk_input_end(&rawfb->ctx);


La gestion même des événements depuis l'écran tactile se produit dans la fonction handle_touchscreen ():



handle_touchscreen
static void handle_touchscreen(struct input_dev *ts, struct fb_info *fb_info,
        struct rawfb_context *rawfb) {
    struct input_event ev;
    int type;
    static int x = 0, y = 0;

    while (0 <= input_dev_event(ts, &ev)) {
        type = ev.type & ~TS_EVENT_NEXT;

        switch (type) {
        case TS_TOUCH_1:
            x = normalize_coord((ev.value >> 16) & 0xffff, 0, fb_info->var.xres);
            y = normalize_coord(ev.value & 0xffff, 0, fb_info->var.yres);
            nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 1);
            nk_input_motion(&rawfb->ctx, x, y);
            break;
        case TS_TOUCH_1_RELEASED:
            nk_input_button(&rawfb->ctx, NK_BUTTON_LEFT, x, y, 0);
            break;
        default:
            break;
        }

    }
}




En fait, c'est là que les événements de périphérique d'entrée sont convertis dans un format que Nuklear comprend. En fait, c'est probablement tout.



Lancer sur une autre carte



Ayant reçu des résultats assez corrects, nous avons décidé de les reproduire sur un autre tableau. Nous avions une autre carte similaire - STM32F769I-DISCO. Il y a le même contrôleur LTDC, mais un écran différent avec une résolution de 800x480. Après son lancement, il a obtenu 25 FPS. Autrement dit, une baisse notable des performances. Cela s'explique facilement par la taille du framebuffer - il est presque 3 fois plus grand. Mais le problème principal s'est avéré différent - l'image était très déformée, il n'y avait pas d'image statique au moment où le widget devrait être au même endroit.



La raison n'était pas claire, nous avons donc examiné des exemples standard de STM32Cube. Il y avait un exemple avec double tampon pour cette carte particulière. Dans cet exemple, les développeurs, contrairement à la méthode avec changement de transparence, déplacent simplement le pointeur vers le framebuffer sur l'interruption VBLANK. Nous avons déjà essayé cette méthode plus tôt pour la première carte, mais cela n'a pas fonctionné pour cela. Mais en utilisant cette méthode pour STM32F769I-DISCO, nous avons obtenu un changement d'image assez fluide à partir de 25 FPS.



Ravis, nous avons testé à nouveau cette méthode (avec réarrangement des pointeurs) sur la première carte, mais elle ne fonctionnait toujours pas à des FPS élevés. En conséquence, la méthode avec des couches transparentes (60 FPS) fonctionne sur une carte et la méthode avec des pointeurs de réarrangement (25 FPS) sur l'autre. Après avoir discuté de la situation, nous avons décidé de reporter l'unification à une étude plus approfondie de la pile graphique.



Résultat



Alors, résumons. L'exemple illustré représente un modèle d'interface graphique simple mais courant pour les microcontrôleurs - quelques boutons, une commande de volume ou autre chose. L'exemple n'a aucune logique associée aux événements, puisque l'accent a été mis sur les graphiques. En termes de performances, nous avons obtenu une valeur FPS assez décente.



Les nuances accumulées pour optimiser les performances conduisent à la conclusion que les graphiques deviennent plus compliqués dans les microcontrôleurs modernes. Maintenant, tout comme sur les grandes plates-formes, vous devez surveiller le cache du processeur, placer quelque chose dans la mémoire externe et quelque chose dans une mémoire plus rapide, utiliser DMA, utiliser DMA2D, surveiller VBLANK, etc. Tout a commencé à ressembler à de grandes plates-formes, et c'est peut-être pour cela que j'ai déjà fait référence à X Server et Wayland à plusieurs reprises.



Peut-être que l'une des parties les moins optimisées est le rendu lui-même, nous redessinons toute la scène à partir de zéro, entièrement. Je ne peux pas dire comment cela se fait dans d'autres bibliothèques pour microcontrôleurs, peut-être quelque part cette étape est intégrée à la bibliothèque elle-même. Mais sur la base des résultats du travail avec Nuklear, il semble que dans cet endroit un analogue de X Server ou Wayland soit nécessaire, bien sûr, plus léger, ce qui nous amène à nouveau à l'idée que les petits systèmes suivent le chemin des grands.



UPD1

En conséquence, la méthode de changement de transparence n'était pas nécessaire. Sur les deux cartes, un code commun fonctionnait - avec l'échange de l'adresse du tampon par v-sync. De plus, la méthode avec les transparents est également correcte, ce n'est tout simplement pas nécessaire.



UPD2

Je tiens à dire un grand merci à toutes les personnes qui ont suggéré la triple mise en mémoire tampon, nous n'y sommes pas encore parvenues. Mais maintenant, vous pouvez voir qu'il s'agit d'une méthode classique (en particulier pour les FPS de fréquences d'images élevées à l'écran), qui, entre autres, nous permettra de nous débarrasser des décalages dus à l'attente de v-sync (c'est-à-dire lorsque le logiciel est nettement en avance sur l'image). Nous n'avons pas encore rencontré cela, mais ce n'est qu'une question de temps. Et un merci spécial pour la discussion sur le triple buffering, je veux direBesitzeruf et belav!



Nos contacts:



Github: https://github.com/embox/embox

Newsletter: embox-ru [at] googlegroups.com

Chat télégramme: t.me/embox_chat



All Articles