windmill-gint/LEPHE.md

9.5 KiB

Voici la liste (chronologique) des changements que j'ai faits pour que ça compile.

Certains changements sont dûs à la nature « incomplète » du programme et j'ai rajouté des initialisations, liens, etc. que tu voudras peut-être organiser différemment. Cherche "(LEPHE)" dans les commentaires pour les trouver.

Je n'ai pas fait le quick sort.

Élimination des warnings

Tout ça se fait dans CMakeLists.txt.

  • Ajouté -Wp,-w à src/map.cpp ; ça indique à GCC de passer -w au préprocesseur, ce qui désactive (entre autres) les avertissements de redéfinition de macros pour ce fichier (normalement il faut #undef les macros avant de les redéfinir).

Pour information: -W<lettre>,<options> passe les options (séparées par des virgules) au programme spécifié par la lettre dans la chaîne de compilation : -Wp passe des options au préprocesseur, -Wa à l'assembleur, et -Wl à l'éditeur de liens.

  • Ajouté -Wno-missing-field-initializers à src/map.cpp, ce qui désactive les avertissements pour les champs non initialisés dans les structures ; attention du coup, si tu en oublies des importants personne ne te le dira.

Déclaration externes des textures constantes

  • Rendu les textures (tex_white, tex_black, etc) non-constantes :
    • Dans src/map.cpp: Texture tex_white = {};
    • Dans src/windmill.cpp extern Texture tex_white;

C'est parce qu'en C++ const implique internal linkage (comme static), et du coup la variable n'est visible que dans le fichier où elle est déclarée. Du coup extern const c'est un peu bizarre, et même si certains bouts m'échappent, le résultat c'est que ça ne marche pas. Il y a probablement une meilleure solution que de couper le const, à rechercher une autre fois.

En déclarant les textures non-constantes les noms sont exportés comme il faut et se retrouvent bien. Du coup les 4 textures arrivent dans la section .bss (RAM initialisée à 0) au lieu de .rodata (ROM) ; si c'est un problème et à défaut d'une autre solution, tu peux mettre GSECTION(".rodata") (de <gint/defs/attributes.h>) devant le Texture et ça les déplacera dans leur section d'origine.

Définition de render_triangle_white

  • Décommenté la vieille fonction render_triangle_white dans src/windmill.cpp. J'ai vu que render_triangle_black avait été redéfinie (réécrite ?), celle-là je n'y ai pas touché.

Test

À ce stade ça compile et le programme ne crashe pas ; l'écran titre a juste une image de titre, et ensuite quand j'appuie sur EXE, écran noir. Pas moyen de sortir, je RESET.

Activation du code qui accède à la VRAM

  • Décommenté tout le code concernant la VRAM ; la VRAM est accessible par la variable gint_vram déclarée dans <gint/display.h>. Sur Graph mono ce n'est pas un tableau de char mais un tableau de uint32_t donc attention ! Quand tu écris char *vram = ... ça ne pose pas de problème, mais gint_vram[i] contrôle 32 pixels d'un coup.

  • Dans src/windmill.hpp, ajouté #define get_vram_address() ((char *)gint_vram).

  • Dans draw_ground(), renommé sol.x et sol.y en ground.x et ground.y dans la ligne suivante.

vram[(ground.y << 4) + (ground.x>>3)] |= mask;

Test

Pareil. J'ai remarqué que le malloc dans Scene_Map::load_map échoue parce que game.map->list_object_length n'est pas initialisé et contient donc une valeur ridicule, et donc le programme gèle pendant l'initialisation de la scène.

Ajout de la fonction de debugging

  • Implémenté debug_pop() dans src/main.cpp et réactivé ses différents usages (sauf les collisions).

Ça c'est pour éviter qu'une allocation échouée ne passe inaperçue. Je l'ai modifié pour accepter non seulement un message mais aussi n'importe quoi qui passe à printf(), histoire d'afficher la taille des allocations qui échouent.

Initialisation de la map

  • Ajouté une initialisation de game.map dans Scene_Map::load_map puisque le pointeur n'est pas initialisé (et la map ne risquait donc pas de charger).

J'ai marqué ce changement "(LEPHE)" parce que je ne sais pas où tu veux le placer. Game::new_game() semblait être une bonne idée mais il n'est jamais appelé actuellement, et les maps ne sont pas visibles dans ce fichier.

  • Initialisé Scene_Map::bbox à nullptr, je ne crois pas qu'il y soit tout seul et tu le free() immédiatement.

Test

À ce stade la map se charge mais j'ai un TLB miss (0x08108048, une adresse 0x48 octets après la fin du segment de données) dans Windmill::draw_post_treatment lors de l'accès à la VRAM.

  • Au passage, j'ai ajouté -Wl,-Map=map dans les options d'édition des liens, ce qui génère automatiquement un fichier build-fx/map avec l'adresse de chaque fonction et variable, ce qui est utile pour les cas comme celui-ci.

Correction des accès à la VRAM dans Windmill::draw_post_treatment

Le problème est l'accès ici :

int address = x + y * z_buffer_width + z_buffer_offset;
// ... z_buffer[address] ...
vram[address<<3] |= mask;
  • address est clairement un numéro de pixel, donc c'est address >> 3 (division par 8) pour indexer le tableau, par << 3 (multiplication par 8).

Test

Avec ça j'obtiens un premier résultat d'image 3D, j'ai la grille au sol, des bouts de texture d'un bâtiment, on progresse !

Options pour quitter

Je sais pas trop comment tu gères tes changements de scènes, visiblement tu avais une variable globale int scene mais elle n'est utilisée nulle part par le programme principal actuellement, donc j'ai commenté les actions qui la modifient.

À la place, j'ai ajouté dans src/main.cpp de quoi quitter avec EXIT et MENU, et j'ai marqué ça "(LEPHE)" puisque je suppose que ce n'est pas le bon endroit. Ça donne d'ailleurs un exemple de l'utilisation de gint_osmenu() pour revenir au menu principal comme dans GetKey() (attention, quand on revient dans l'add-in une image statique est affichée, ton code ne reprend pas avant qu'une touche soit pressée).

Note sur l'activation des entrées clavier

Je ne sais plus exactement comment ta bibliothèque input fonctionnait, mais je soupçonne entre input_trigger() et input_press() que parfois tu ne veux activer ta condition qu'aun moment où la touche descend de la position relâchée à la position enfoncée.

Pour faire ça, tu lis les événements un par un avec pollevent() et tu regardes s'il y a un KEYEV_DOWN correspondant à ta touche. Lorsqu'il n'y a plus d'événements à lire, pollevent() renvoie un événément de type KEYEV_NONE.

key_event_t event;

while((event = pollevent()).type != KEYEV_NONE)
{
    if(event.type == KEYEV_DOWN && event.key == KEY_F8)
    {
        /* F8 vient d'être enfoncée */
    }
}

Pour simplement savoir si une touche est pressée, utilise keydown() après avoir lu tous les événements (soit avec clearevents() soit avec pollevent() dans une boucle comme ci-dessus).

if(keydown(KEY_F8))
{
    /* F8 est actuellement pressée (depuis longtemps potentiellement) */
}

Attention la fonction keydown() n'est pas comme IsKeyDown() : elle ne regarde pas l'état absolu de la touche, mais l'état de la touche tel qu'indiqué par les événements qui ont été lus. D'où l'importance de lire tous les événements avant pour se synchroniser avec l'état absolu du clavier avant de l'utiliser. Ça peut paraître casse-pieds mais en fait c'est fondamental, ça fait marcher très élégamment les interactions avec getkey().

Comme tu utilises input_trigger() dans plusieurs méthodes il n'y a pas de façon évidente de tester les événements (tu ne peux les lire qu'une fois, c'est un flux) donc je n'ai pas touché aux interactions avec EXE, et les F2/F3. Si tu tiens à le gérer dans deux méthodes différentes tu peux recoder ce que tu faisais dans input.c. Mais honnêtement si j'étais toi je mettrais l'analyse des événements dans une seule méthode et ensuite si les événements se produisent tu répartis les actions.

Activation des contrôles du joueur

Pour le contrôle du joueur (plus facile puisqu'il n'utilise que input_press()), j'ai utilisé keydown() en remplaçant input_move() par une analyse rapide des touches de chiffres 4, 5 et 6. J'ai mis un "(LEPHE)" là-dessus au cas où tu veuilles le déplacer ailleurs. La gestion de la rotation avec les touches fléchées n'a pas demandé de changements particuliers.

J'ai commenté le debug_display("trop loin") parce que je ne suis pas sûr de ce que tu affiches avec cette fonction (pas que des chaînes de caractères apparemment) donc je ne pouvais pas la recoder.

Pour l'instant j'ai mis un clearevents() dans src/main.cpp (pour gérer EXIT/MENU) et ça fait marcher le keydown() dans le contrôleur du joueur, mais quand tu réarrangeras src/main.cpp n'oublie pas de lire les événements quelque part avant d'utiliser keydown() (et donc il faut gérer les interactions de type "trigger" avant les appuis continus oui).

Test

Ça marche, on peut bouger, tourner, sauter, etc. J'ai pas cherché à analyser les performances, mais tu seras peut-être content de savoir qu'il existe une bibliothèque pour gint, libprof [1,2], qui peut mesurer le temps passé dans chaque fonction à la microseconde près.

En tous cas ça a l'air décent niveau perfs, je pense que tout y est ou presque.

TODO list

Je n'ai pas touché au tri des objets; qsort() n'est pas encore implémenté dans notre lib standard. Ça ne devrait pas tarder parce que ce n'est pas bien difficile, mais ce n'est pas encore prêt.

Au passage les en-têtes gint sont protégés pour passer en C++ maintenant. Pas besoin de mettre extern "C" {} autour.