Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » Zlib pour Casio fx/cg (développement et benchmark)
Slyvtt Hors ligne Community Manager Points: 891 Défis: 0 Message

Zlib pour Casio fx/cg (développement et benchmark)

Posté le 01/05/2022 11:20

Hello à Toutes et Tous

Dans le long voyage qui nous amènera peut-être (enfin j'espère bien) à avoir une librairie SDL complète (avec SDL_image), il y a un certain nombre de prérogatives à passer, notamment pour le support des ressources externes en format PNG. En effet la SDL_image s'appuie sur la libPNG, qui elle même repose sur la zlib. Donc il faut prendre les choses les unes après les autres et je vous ai créé un dépôt avec la zlib fonctionnelle pour les Casio (fx/cg).

La zlib, mais kézako ? Pour ceux qui ne connaissent pas, il s'agit d'une bibliotheque de fonctions visant à offrir des algorithmes de compression / décompression sans perte de données. Cette bibliothèque est très largement utilisée par de nombreux logiciels (sur à peu près toutes les plateformes existantes), y compris par l'OS de nos Casio chéries. En effet, l'OS contient quelques syscall faisant référence à cette zlib, mais hélas à ce stade nous ne savons pas les utiliser car peu ou prou documentés.

Donc notre zlib opérationnelle se nomme cZlib (contraction de "Casio Zlib") et repose sur la zlib version 1.2.5. Le dépôt Gitea de la bête se trouve ici : cZlib1.2.5

Comme d'habitude il est possible d'installer la libczlib via GiteaPC par la commande suivante
giteapc install Slyvtt/cZlib1_2_5
la compilation se faisant à la volée ainsi que l'installation dans les répertoires adhoc du compilateur contenant les headers et les librairies. La encore, si vous avez des soucis, passer sur fxlibc@dev, car il se peut que certaines features récentes soit nécessaires et pas encore dans la branche master.

Pour les plus aventuriers, il y a aussi un Makefile à lancer dans le répertoire racine d'extraction via un
make -f Makefile.prizm
Il vous faudra alors copier manuellement libczlib.a dans le répertoire d'installation de vos lib de votre compilateur et les deux fichiers headers zlib.h et zconf.h contenus dans le répertoire racine dans le sous dossier include de votre compilateur.


Là encore comme d'hab', vous pourrez commencer à créer un programme avec la zlib très simplement.
Créer un nouveau projet via
fxsdk new SDLproject
puis vous devrez ouvrir le fichier CMakeLists.txt contenu dans le projet pour éditer la ligne
target_link_libraries(myaddin Gint::Gint)
et la remplacer par
target_link_libraries(myaddin Gint::Gint -lczlib)
(bref ajouter la librairie qui va bien).

voici un code exemple pour utiliser :


#include <gint/gint.h>
#include <gint/display.h>
#include <stdio.h>
#include <string.h>  // for strlen
#include <assert.h>
#include "zlib.h"

int main()
{  
    // original string to be compressed
    char a[50] = "Hello Hello Hello Hello Hello Hello !!!!!!";
    // placeholder for the compressed (deflated) version of "a"
    char b[50];
    // placeholder for the UNcompressed (inflated) version of "b"
    char c[50];
    
    dclear( 0xFFFF );
    dprint(1,10,0x0000, "Uncompressed size is: %lu", strlen(a));
    dprint(1,20,0x0000, "Uncompressed string is:");
    dprint(1,30,C_BLUE, "%s", a);
    
    // STEP 1 : deflate a into b. (that is, compress a into b)  
    // zlib struct
    z_stream defstream;
    defstream.zalloc = Z_NULL;
    defstream.zfree = Z_NULL;
    defstream.opaque = Z_NULL;
    // setup "a" as the input and "b" as the compressed output
    defstream.avail_in = (uInt)strlen(a)+1; // size of input, string + terminator
    defstream.next_in = (Bytef *)a; // input char array
    defstream.avail_out = (uInt)sizeof(b); // size of output
    defstream.next_out = (Bytef *)b; // output char array
    // the actual compression work.
    deflateInit(&defstream, Z_BEST_COMPRESSION);
    deflate(&defstream, Z_FINISH);
    deflateEnd(&defstream);    

    dprint(1,50,0x0000, "Compressed size is: %lu", strlen(b));
    dprint(1,60,0x0000, "Compressed string is:" );
    dprint(1,70, C_BLUE, "%s", b);  
    dprint(1,80,C_RED, "Don't worry if not all char are visible :");
    dprint(1,90,C_RED, "may contain non printable ones" );

    // STEP 2 : inflate b into c (should return to string a)  
    // zlib struct
    z_stream infstream;
    infstream.zalloc = Z_NULL;
    infstream.zfree = Z_NULL;
    infstream.opaque = Z_NULL;
    // setup "b" as the input and "c" as the compressed output
    infstream.avail_in = (uInt)((char*)defstream.next_out - b); // size of input
    infstream.next_in = (Bytef *)b; // input char array
    infstream.avail_out = (uInt)sizeof(c); // size of output
    infstream.next_out = (Bytef *)c; // output char array
    // the actual DE-compression work.
    inflateInit(&infstream);
    inflate(&infstream, Z_NO_FLUSH);
    inflateEnd(&infstream);
    
    dprint(1,110,0x0000, "Uncompressed size is: %lu", strlen(c));
    dprint(1,120,0x0000, "Uncompressed string is:" );
    dprint(1,130,C_BLUE, "%s", c);

    // make sure uncompressed is exactly equal to original.
    if(strcmp(a,c)==0)     dprint(1,150, C_GREEN, "Everything is OK" );
    else dprint(1,150, C_RED, "There is a problem somewhere" );
        
    dupdate();
    getkey();
    return 0;
}


Et voici deux screenshots pris sur ma G90+E pour montrer que ça marche :



Fun fact : vous noterez l'efficacité de la compression dans le premier cas

Cela pourra toujours vous servir pour des utilitaires à un moment ou à un autre.
Attention, comme d’habitude avec les flux dans les fichiers : bien penser au gint_world_switch qui va bien !!!

Afin de mesurer les performances de la cZlib sur nos machines, j'ai testé différentes configurations sur divers fichiers. Le protocole de test est le suivant :
- ouverture du fichier et stockage de l'ensemble des données contenues dans celui-ci en mémoire dans un flux (stream au sens de la zlib) d'entrée ("inputStream").
- compression de ce flux d'entrée via la fonction "compress()" de la zlib et stockage dans un flux intermédiaire ("outputStrean") avec mesure du temps de compression et la taille du flux compressé / du ratio de compression.
- décompression du flux intermédiaire vers le flux final ("secondOutputStream") via la fonction "uncompress()" de la zlib, là encore avec mesure du temps de décompression et la taille du flux décompressé (qui doit retomber sur la taille initiale, sinon il y a un problème).
- validation octet par octet de la similitude de "inputStream" et de "secondOutputStream" (on regarde que le cycle compression/décompression est sans erreur).
- écriture sur disque du flux decompressé finale pour vérifier que l'on peut utiliser le fichier sans soucis (a priori redondant avec étape précédente, mais on est jamais assez prudent ).

A ce stade les tests sont réalisés sur une fx-CG 50 (ou Graph 90+E) sans overclock. Je pense complèter par d'autres essais plus tard. Les temps sont mesurés avec la LibProf.

Les fichiers tests sont :
- 3 fichiers Addins (des ".g3a") de diverses tailles : Conv.g3a (~29ko) et PicPlot.g3a (~83ko) de Casio et Afterburner de Lephé (~172ko)
- 1 fichier texte (".txt") créé en ligne par un générateur de phrases aléatoires en français : texte0.txt (~36ko)
- 3 fichiers images (".bmp") sans compression : image0.bmp (~873ko) photo 660x441pix en 24bpp, image1.bmp (~265ko) image de fond de mon jeu OutRun 396x224pix en 24bpp, et image2.bmp (~57ko) convertie depuis une fonte fxconv, image en N&B,
- 3 fichiers photos (".png") avec compression : photo0.png (~26ko), photo1.png (~194ko), photo3.png (~579ko)

Et sans plus attendre voici les résultats obtenus :



Il est intéressant de voir les niveaux de compression et surtout les temps de décompression qui sont vraiment pas mauvais du tout et pourrait laisser envisager quelques trucs sympas dans Gint.

Code utilisé (Cliquer pour dérouler)
Code utilisé (Cliquer pour enrouler)


#include <gint/gint.h>
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/hardware.h>
#include <gint/kmalloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>  // for strlen
#include <assert.h>
#include <sys/stat.h>
#include "zlib.h"
#include <libprof.h>


char *filesin[10] = { "photo0.png", "photo1.png", "photo2.png", "Conv.g3a", "PictPlot.g3a", "Afterbur.g3a", "texte0.txt", "image0.bmp", "image1.bmp", "image2.bmp" };
char *filesout[10] = { "Z_photo0.png", "Z_photo1.png", "Z_photo2.png", "Z_Conv.g3a", "Z_PictPlot.g3a", "Z_Afterbur.g3a", "Z_texte0.txt", "Z_image0.bmp", "Z_image1.bmp", "Z_image2.bmp" };
struct stat sb;

uint32_t time_compress=0, time_decompress=0;

uint32_t sizeStream;
char *inputStream = NULL;
char *outputStream = NULL;
char *secondOutputStream = NULL;

int OpenFileAndLoadStream( int which )
{
    if (inputStream!=NULL) {
        free( inputStream );
        inputStream = NULL;
    }
    if (outputStream!=NULL) {
        free(outputStream );
        outputStream = NULL;
    }
    if (secondOutputStream!=NULL) {
        free( secondOutputStream );
        secondOutputStream = NULL;
    }

    if (stat(filesin[which], &sb) == -1) {
        return -1;
    }

    sizeStream = (uint32_t) sb.st_size;

    inputStream = (char*) malloc( sb.st_size );
    if (inputStream == NULL) {
        return -2;
    }

    outputStream = (char*) malloc( sb.st_size+10000 );
    if (outputStream == NULL)
    {
        free(inputStream);
        inputStream = NULL;
        return -3;
    }

    secondOutputStream = (char*) malloc( sb.st_size );
    if (secondOutputStream == NULL)
    {
        free(outputStream);
        inputStream = NULL;
        free(outputStream);
        inputStream = NULL;
        return -4;
    }

    FILE* fp = fopen(filesin[which], "rb" );
    if (fp==NULL) {
        return -5;
    }

    fread( inputStream, sb.st_size, 1, fp );

    fclose ( fp );

    return 1;
}

int SaveOutputFile( int which )
{
    FILE* fp = fopen(filesout[which], "wb" );
    if (fp==NULL) {
        return -5;
    }

    fwrite( secondOutputStream, sizeStream, 1, fp );

    fclose ( fp );

    return 1;
}


void CloseAndFree( void )
{
    if (inputStream!=NULL) {
        free( inputStream );
        inputStream = NULL;
    }
    if (outputStream!=NULL) {
        free( outputStream );
        outputStream = NULL;
    }
    if (secondOutputStream!=NULL) {
        free( secondOutputStream );
        secondOutputStream = NULL;
    }
    exit(0);
}


bool canWeAllocate3Mb = false;
static kmalloc_arena_t extended_ram = { 0 };

void increaseRAM( void )
{
    char const *osv = (char*) 0x80020020;

    //kmalloc_gint_stats_t *extram_stats;

    if((!strncmp(osv, "03.", 3) && osv[3] <= '6') && gint[HWCALC] == HWCALC_FXCG50) // CG-50
    {
        extended_ram.name = "extram";
        extended_ram.is_default = true;
        extended_ram.start =   (void *)0x8c200000;
        extended_ram.end = (void *)0x8c500000 ;

        kmalloc_init_arena(&extended_ram, true);
        kmalloc_add_arena(&extended_ram );
        canWeAllocate3Mb = true;
    }
    else if (gint[HWCALC] == HWCALC_PRIZM)  // CG-10/20
    {

        extended_ram.name = "extram";
        extended_ram.is_default = true;

        uint16_t *vram1, *vram2;
        dgetvram(&vram1, &vram2);
        dsetvram(vram1, vram1);

        extended_ram.start = vram2;
        extended_ram.end = (char *)vram2 + 396*224*2;

        kmalloc_init_arena(&extended_ram, true);
        kmalloc_add_arena(&extended_ram );
        canWeAllocate3Mb = false;

    }
    else if (gint[HWCALC] == HWCALC_FXCG_MANAGER) // CG-50 EMULATOR
    {

        extended_ram.name = "extram";
        extended_ram.is_default = true;
        extended_ram.start =   (void *)0x88200000;
        extended_ram.end = (void *)0x88500000 ;

        kmalloc_init_arena(&extended_ram, true);
        kmalloc_add_arena(&extended_ram );
        canWeAllocate3Mb = true;

    }
    else abort();
}



int main()
{
    uint16_t *vram1, *vram2;
    dgetvram(&vram1, &vram2);
    dsetvram(vram1, vram1);

    increaseRAM();

    prof_init();
    prof_t perf_compress, perf_decompress;

    int file=9;

    int retour = (int) gint_world_switch( GINT_CALL(OpenFileAndLoadStream, file) );

    if (retour<=0) CloseAndFree();

    uint32_t ucompSize = sizeStream; // "Hello, world!" + NULL delimiter.
    uint32_t compSize = compressBound(ucompSize);
    uint32_t reucompSize = sizeStream; // "Hello, world!" + NULL delimiter.

    perf_compress = prof_make();
    prof_enter(perf_compress);
    // Deflate
    compress((Bytef *)outputStream, &compSize, (Bytef *)inputStream, ucompSize);

    prof_leave(perf_compress);
    time_compress = prof_time(perf_compress);


    perf_decompress = prof_make();
    prof_enter(perf_decompress);
    // Inflate
    uncompress((Bytef *)secondOutputStream, &reucompSize, (Bytef *)outputStream, compSize);

    prof_leave(perf_decompress);
    time_decompress = prof_time(perf_decompress);


    dclear( 0xFFFF );
    dprint(1,10,0x0000, "Uncompressed size is: %lu", ucompSize );
    dprint(1,20,0x0000, "Compressed size is: %lu", compSize );
    dprint(1,30,0x0000, "Uncompressed size is: %lu", reucompSize );

    dprint(1,40, C_GREEN, "time to compress: %lu", time_compress/1000 );
    dprint(1,50, C_GREEN, "time to decompress: %lu", time_decompress/1000 );

    dupdate();

    for( uint32_t k=0; k<sizeStream; k++ )
    {
        if (inputStream[k]!=secondOutputStream[k])
        {
                dprint(1,70,C_RED, "Error at byte %d", k );
                break;
        }
    }

    dprint(1,90,C_BLUE, "End of comparison" );
    dprint(1,100, 0x0000, "Now Saving Ouput ... " );
    dupdate();


    retour = (int) gint_world_switch( GINT_CALL(SaveOutputFile, file ) );

    dprint(1,110, 0x0000, " ... Done :-) " );
    dprint(1,130, 0x0000, "Press a key to exit" );

    dupdate();

    getkey();

    // We set back zeros at the end of the program
    memset(extended_ram.start, 0, (char *)extended_ram.end - (char *)extended_ram.start);

    return 0;
}



Ciao

Sly

Et zou, @RDP


Lephenixnoir Hors ligne Administrateur Points: 22762 Défis: 149 Message

Citer : Posté le 02/05/2022 16:52 | #


Yo, alors je suis assez intéressé par les taux personnellement.

J'ai réfléchi à la question de compresser les add-ins. Après tout, il y a pas mal de code, alors autant en profiter pour réduire les temps de transfert. Le problème crucial c'est que l'OS n'allouera des pages que jusqu'à la taille du fichiers g3a, ce qui veut dire en termes pratiques que on ne peut pas compresser ce qui doit vivre dans la ROM à l'exécution.

En particulier si on compresse des images (la toute première cible évidemment), il faudra les décompresser dans la RAM, ce qui retire la possibilité de s'appuyer sur les 2 Mo de l'add-in pour stocker des choses. Si on s'autorise l'accès à la RAM au-delà des 512 ko et jusqu'à 3/6 Mo ça ne devrait pas être un problème, cela dit.

Cette approche permettrait aussi de décompresser à la demande, par exemple d'avoir en RAM les données du niveau courant uniquement.

Pour ce qui est du format, le plus évident de loin est de compresser les tableaux de pixels des images bopti. Je vois pas trop de raison de s'embêter avec du PNG sauf pour porter des programmes qui s'en servent déjà. En tous cas la réduction potentielle de taille des add-ins est assez intéressante, surtout en termes de temps de transfert.
Slyvtt Hors ligne Community Manager Points: 891 Défis: 0 Message

Citer : Posté le 02/05/2022 19:49 | #


juste j'ai oublié de préciser que le compression ratio est en % et qu'il est calculé par la formule suivante :

Cr% = (taille_initiale - taille_apres_compression) / taille_initiale * 100

Logique, mais mérite d'être explicité clairement.

------------------------------------------------------------------
Le monde est dangereux à vivre ! Non pas tant à cause de ceux qui font le mal, mais à cause de ceux qui regardent et laissent faire.
-------------------------------------------------------------------
Albert Einstein
Mathématicien, Physicien, Scientifique (1879 - 1955)
Slyvtt Hors ligne Community Manager Points: 891 Défis: 0 Message

Citer : Posté le 05/05/2022 09:38 | # | Fichier joint


Petite update sur l'utilisation de la Zlib sur Prizm/Graph 90+E.

Grosso modo le moteur est fonctionnel et la lib est utilisable, mais il y a des cas plus exotiques qu'il faut traiter et/ou tester.

En ce moment, je suis en train de regarder pour compresser de gros fichiers en utilisant un "flux" glissant en entrée et en sortie, mais pour le moment j'ai quelques problèmes de crash.

L'idée derrière cela serait d'avoir une allocation mémoire minimale (de seulement quelques Ko correspondant à un "chunk") qui serait mis à jour dans la routine de compression et/ou de décompression jusqu'à ce que le fichier complet soit compressé ou décompressé. Actuellement je charge l'intégralité du contenu du fichier à traiter en RAM donc il y a de vraiment grosses allocations de mémoires nécessaires.

Cela ne pose en soit pas de problème sur la G90+E car on a de la marge en RAM en utilisant les ressources "non officielles", mais peut s'avérer déjà plus complexe sur les prizm (CG10/20) et très très complexe sur les monochrome a priori. Donc cela limiterait la taille des fichiers manipulables, faute de pouvoir les charger complètement en RAM pour opérer dessus par la suite.

J'espère donc pouvoir vous apporter quelques bonnes nouvelles rapidement avec un fichier exemple de l'utilisation de la lib (car c'est pas forcément très très limpide je reconnais ).

Sly

@RDP

------------------------------------------------------------------
Le monde est dangereux à vivre ! Non pas tant à cause de ceux qui font le mal, mais à cause de ceux qui regardent et laissent faire.
-------------------------------------------------------------------
Albert Einstein
Mathématicien, Physicien, Scientifique (1879 - 1955)

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v42 © créé par Neuronix et Muelsaco 2004 - 2022 | Il y a 57 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd