[TUTO HOMEBREW] Faire son premier homebrew, et surtout le comprendre
Salut, venant de comprendre une bonne partie du lancement/fermeture de programme pour la ps3, je vous fais un petit tuto, pour que d'autres qui ne connaissent pas très bien openGL puissent démarrer concretement plus rapidement.
Le but de ce tuto sera d'initialiser le contexte graphique, d'executer une boucle interruptible par le bouton PS/quitter le jeu, puis de répondre à l'interruption en revenant proprement sur le XMB.
Ce tutorial est utile aux personnes connaissant le C et l'OpenGL, et je développe une petite librairie en C++ pour abstraire tout ce qui pourrait l'être (polygones, brush, manette, affichage de chaines de charactères, reseau, etc).
Tout d'abord, petite introduction, inspirée de diverses présentations trouvables sur les sites officiels de Sony: comment la PS3 dessine-t-elle à l'écran.
Il y a deux librairies disponibles pour les développeurs: GCM et PSGL, GCM étant le pendant bas-niveau de PSGL. J'ai décidé d'utiliser PSGL car on n'a pas vraiment besoin d'un plus bas niveau pour développer des petits homebrew, mais sachez que le développeur anonyme de l'open backup manager utilise GCM, ce qui rend son code difficile à comprendre et trop complexe pour ce qu'il fait en réalité (ce qui est peut-être fait expres).
PSGL est une implémentation made in Sony d'OpenGL ES, une version simplifiée d'OpenGL (110 primitives au lieu de 400) mais qui d'après eux reste aussi puissante. Nous nous contenterons ici de parler des mecanisme d'initialisation et de fermeture, car l'utilisation d'OpenGL n'est pas propre à la PS3 et il existe déja des centaines de cours bien construits.
Enfin, il faut savoir qu'il n'existe aucun autre moyen que ces librairies graphiques pour afficher quelque chose à l'écran, donc pas de sortie standard, pas de printf etc.
Mon tutorial sera sous la forme d'un code commenté pour chaque étape.
Première partie - initialiser la PS3 -
Citation:
// On demande 1 spu pour PSGL sur les 6 disponibles
sys_spu_initialize(6, 1);
// Met en pause la PS3 tant que la sortie HDMI n'est pas prête.
CellVideoOutState videoState;
do {
cellVideoOutGetState(CELL_VIDEO_OUT_PRIMARY, 0, &videoState);
} while(videoState.state != CELL_VIDEO_OUT_OUTPUT_STATE_ENABLED );
// Tableau des résolution désirées, par ordre de priorité.
const unsigned int resolutions[][3] = {
{CELL_VIDEO_OUT_RESOLUTION_1080, 1920, 1080},
{CELL_VIDEO_OUT_RESOLUTION_720, 1280, 720},
{CELL_VIDEO_OUT_RESOLUTION_576, 720, 576},
{CELL_VIDEO_OUT_RESOLUTION_480, 720, 480}
};
// On choisi la résolution disponible et on enregistre son index dans le
// tableau précédent.
int chosenResolutionIndex = -1;
for (int i = 0; i < 4 && chosenResolutionIndex == -1; i++) {
if(cellVideoOutGetResolutionAvailability(CELL_VIDE O_OUT_PRIMARY,
resolutions[i][0],
CELL_VIDEO_OUT_ASPECT_AUTO,
0)) {
chosenResolutionIndex = i;
}
}
// On peut maintenant passer à l'initialisation de PSGL
if (chosenResolutionIndex != -1) {
// On cherche à obtenir les information de largeur et longueur de la
// résolution choisie.
unsigned int width = resolutions[chosenResolutionIndex][1];
unsigned int height = resolutions[chosenResolutionIndex][2];
// On crée une sortie PSGL paramétrée avec nos dimensions.
PSGLdeviceParameters params;
// On choisi les parametres a faire prendre en compte par createDevice
params.enable = PSGL_DEVICE_PARAMETERS_COLOR_FORMAT
| PSGL_DEVICE_PARAMETERS_DEPTH_FORMAT
| PSGL_DEVICE_PARAMETERS_MULTISAMPLING_MODE
| PSGL_DEVICE_PARAMETERS_WIDTH_HEIGHT;
// 4 composantes disponibles pr les vertex
params.colorFormat = GL_ARGB_SCE;
// 24 bits dispo pour les couleurs
params.depthFormat = GL_DEPTH_COMPONENT24;
params.multisamplingMode = GL_MULTISAMPLING_NONE_SCE;
params.width = width;
params.height = height;
// On initialise le Viewport - Partie OpenGL indispensable
// On cherche à obtenir les dimensions effectives de dessin.
GLuint renderWidth, renderHeight;
psglGetRenderBufferDimensions(device, &renderWidth, &renderHeight);
// On fixe la taille du Viewport
glViewport(0, 0, renderWidth, renderHeight);
// On met en place la projection orthogonale.
GLfloat aspectRatio = psglGetDeviceAspectRatio(device);
float l=aspectRatio, r=-l, b=-1, t=1;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(l,r,b,t,0,1);
// Il faut effacer au moins une fois l'ecran.
glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
| GL_STENCIL_BUFFER_BIT);
psglSwap();
// On enregistre l'event handler sur le slot 0.
cellSysutilRegisterCallback(0, systemCallback, NULL);
}
La dernière ligne prépare la prochaine étape: l'execution de la boucle principale.
En effet comme dans toute application OpenGL, on doit nous même boucler pour que le programme de s'arrête pas. Cependant il existe un cas ou nous aimerions que le programme s'arrête: quand l'utilisateur éteind la ps3 ou demande au programme de s'arreter via sa manette. Pour cela, on utilise un gestionnaire d'evenement (event handler) qui appelera a chaque evenement une fonction "réponse" (callback) qu'il faut enregistrer au préalable grace à l'appel cellSysutilRegisterCallback.
Le premier argument de cette fonction est le "slot", il y en a 4 disponibles, de 0 à 3. Le dernier argument est un pointeur vers un buffer contenant diverse information utiles au callback, mais je n'en ai pas vraiment besoin, je le met donc à NULL. Enfin, le deuxieme argument est une fonction à executer quand un évènement aura eu lieu.
Deuxieme Etape - Execution de la boucle principale
Juste apres l'initialisation du contexte, on doit executer une boucle interruptible comme cela:
Citation:
while(!systemExited) {
cellSysutilCheckCallback(); // On vérifie les interruptions
// ... On execute des instructions de dessin
}
Si un evenement a lieu, une fonction enregistrée comme callback sera appelée, en voici un exemple:
Citation:
void systemCallback(const uint64_t status, const uint64_t param,
void *userdata) {
(void)userdata; // Pour supprimer les warnings
(void)param;
switch (status) {
case CELL_SYSUTIL_REQUEST_EXITGAME: systemExited = true;
break;
case CELL_SYSUTIL_DRAWING_BEGIN:
case CELL_SYSUTIL_DRAWING_END:
default: break;
}
}
Le prototype doit être exactement identique (3 arguments, renvoie void).
L'arguement status est le code d'interruption, on cherche donc lequel c'est grace à un branchement conditionnel, et si c'est une requête de sortie de programme, on modifie la valeur de systemExited, qui lui meme permettra d'arréter la boucle.
Une fois la boucle interrompue, on doit fermer le contexte OpenGL pour eviter que la PS3 bug completement et retourne plutot calmement au XMB:
Ces primitives parlent d'elles-même. A noter que pour compiler vous aurez besoin des header <psgl/psgl.h> <cell/sysmodule.h> et <sys/spu_initialize.h>.
Si ce tuto vous a plu, n'hésitez pas à m'en demander d'autre, comme la création d'un Makefile efficace, je prépare aussi la gestion complete de la manette.
Si vous avez le sentiment que mon code pourrait être simplifié/amélioré dites-le moi aussi
__________________
le loup est un loup pour le loup
Nintendo: N64, NGC
Sega: Dreamcast
Sony: PSP, PS3
Ces 4 utilisateurs disent Merci à Wolfi pour ce poste utile:
Je lirais ça tête reposée, ça a l'air très intéressant
Ca l'est! Je suis en train de terminer l'abstraction des manettes, je pense poster dans pas trop longtemps un petit programme complet, basé sur un exemple du sdk qui fait tourner une grille colorée, mais en permettant de la faire tourner dans l'autre sens quand on appuie sur une touche et de la stopper quand on appuie sur start Et avec un peu de chance, de meme faire vibrer la manette quand on fait reprendre la rotation, ca permettra de voir comment fonctionne la manette et la librairie que je fais sera ultra facile à utiliser puisque toute en objet, contrairement à ce que j'ai pu voir des autres homebrew qui font tout en impératif.
__________________
le loup est un loup pour le loup
Alors ça c'est réellement quelque chose d'utile ! Un bon topic comme on en vois trop peu ! Un grand merci a toi de vouloir nous faire partager (et surtout, de vouloir nous faire apprendre) ces principes de base pour la PS3
Je suis d'accord, j'ai cherché partout des mini tuto pr démarrer, mais tous les gars qui savent partagent pas, ceux qui partagent ont un code flou, mal commenté, avec parfois des trucs clairement cachés (la fonction "syscall36" de l'open backup manager est pas trop débutant-friendly xD).
Donc j'ai eu envie, au cas incroyable ou d'autres seraient dans mon cas (savent coder en C/C++/OpenGL mais pas assez pr piger direct la doc du SDK).
Et puis même pr les gros gros débutants (savent coder un peu, mais l'openGL c'est du chinois) ca peut être marrant de voir et d'avoir le sentiment de comprendre un petit bout du codage de homebrew, qui pour une fois est plutôt facile.
En tout cas j'ai pigé la manette, je poste un mini tuto tres bientot
__________________
le loup est un loup pour le loup
Comme promis, voila un deuxième tuto pour comprendre le fonctionnement de la librairie libpad, gérant la manette de la PS3.
Première partie - Initialiser la librairie
Comme pour toutes les librairie (qui sont des prx chargés automatiquement au lancement d'un programme linké statiquement avec une librairie stub) il faut l'initialiser:
Citation:
if (cellPadInit(7) == CELL_OK){
padInitialized = true;
}
Cette fois-ci, c'est très simple, il suffit d'appeler cette fonction, avec en argument le nombre maximal de pad que l'on veut gérer (entre 1 et 7).
Deuxieme partie - Obtention des informations de la manette
Il y a plusieurs choses à savoir pour utiliser la manette. Déja, plusieurs types de
manettes sont disponibles, la sixaxis simple, la dualshock 3 et meme des manette sans pression analogique des boutons (type PS1, soit c'est appuyé, soit ça ne l'est pas, impossible de savoir à quel point).
Ensuite, ou et quand demander les informations: dans la boucle principale, avant ou après le dessin, et a chaque passage dans la boucle il faudra mettre à jour ces informations.
Passons au code, que j'ai simplifié pour la bonne compréhension:
cellPadGetData a comme premier argument le numéro de la manette que l'on veut observer, ici 0, et comme second argument une structure à deux champs, len et buttons, et cette structure sera remplie pendant l'appel de la fonction avec les informations de la manette.
Le champ len est très important, il permet de savoir exactement à quel type de manette on a affaire, au lieu d'utiliser les methodes spécialisées pour cela, même si ce n'est pas le but premier. len donne en fait la taille du tableau buttons, qui selon sa taille contient des information de pression des boutons, de degré de pression, puis de mouvement du pad. Ce qui va nous interesser ici, c'est de savoir si le bouton "CROIX" est appuyé: on doit donc tester si len est supérieur à 0 (sinon la manette n'est pas encore disponible), et ensuite verifier si le troisième élément du tableau peut être masqué par la valeur du bouton CROIX:
Citation:
if(padData.len > 0) { //Longueur inf ou égale à zero -> manette déconnectée
if (padData.buttons[3] & CELL_PAD_CTRL_CROSS) { // On teste avec le masque du bouton croix, les masques étant toujours: CELL_PAD_CTRL_*nom du bouton*
// Le bouton croix a été appuyé, on réagit en conséquence.
}
}
Troisieme partie - Fermer la librairie
Pour fermer la librairie, après la boucle principale il suffit d'executer:
Citation:
cellPadEnd();
Voila, c'est tout pour le polling de manette, qui est plutot simple, et il est bien possible que la gestion du clavier et de la souris le soit tout autant.
Enfin, pour completer avec un exemple concret, pour ceux qui voudrait voir comment on peut coder ça en C++ et pas en impératif basique (avec aussi un superbe Makefile très inspiré de celui de l'open backup manager, qui la a bien géré) je vous joint un petit programme à compiler vous-meme (puisque distribuer des pkg semble interdit), c'est une grille moche qui tourne (exemple du SDK) à laquelle j'ai ajouté la possibilité de stopper avec START et faire changer de direction de rotation avec les fleches gauche et droite, le tout refait en vrai C++ . Petite note pour la compilation, je n'ai pas réussi a desactiver la levée d'exception dans la STL donc vous aurez à commenter la ligne qui rejette une erreur dans l'header de l'objet list de la STL du SDK (si quelqu'un a une idée à ce propos ...)
Je sais que je t'ai déjà posé certaines questions et je te remercie de tes réponses.
J'ai cru comprendre également que tu n'avais pas beaucoup de temps mais accepterais-tu de nous faire un post sur la mise en place d'un environnement de développement ?
Je pense (mais je peux me tromper) que plusieurs personnes seraient intéressées.
Merci d'avance de toutes réponses (positives ou négatives) que tu pourrais nous fournir.
Pour l'environnement, perso j'utilise eclipse. Tout est déja fait, faut juste savoir utiliser les makefile avec eclipse, ce qui est décrit dans beaucoup de bien meilleurs tuto que ce que je pourrais faire moi-même. Donc c'est pas vraiment un problème.
Si tu parles de la toolchain, sony file un pdf avec tout expliqué, et de toute on a pas vrt le droit d'en parler
Citation:
Envoyé par Sphax
Bonsoir,
Je sais que je t'ai déjà posé certaines questions et je te remercie de tes réponses.
J'ai cru comprendre également que tu n'avais pas beaucoup de temps mais accepterais-tu de nous faire un post sur la mise en place d'un environnement de développement ?
Je pense (mais je peux me tromper) que plusieurs personnes seraient intéressées.
Merci d'avance de toutes réponses (positives ou négatives) que tu pourrais nous fournir.
Sph@x
__________________
le loup est un loup pour le loup