Envoyé par
Dr.Who
HAAAA MY EYES!
bien joué, une petite dentro bien sympa, dommage il manque un petit mod en fond histoire de.
attention par contre, le texte semble défilé plus ou moins vite sur certaines machine ce qui donne mal au yeux au bout d'un moment ... trop de texte.
faut la poster sur Pouet.net
si tu veux y ajouter un petit mp3 style chiptune fait moi signe.
Merci pour le qualificatif "Dentro", car il me semble qu'une Intro ne doit pas dépasser 64 K
Merci bien d'avoir essayé sur plusieurs machines peux-tu me dire lesquelles étaient-ce ?
Normalement le scroll text ainsi que tous les autres déplacement sont synchronisés avec la fin du balayage vertical, ce qui veut dire que les déplacements doivent être parfaits !... et ne doivent pas faire mal aux yeux. Mais ça veut dire que j'ai pas pris en compte toutes les différences des matériels existant
Je viens de jeter un oeil sur Pouet.net, je vais la poster aussi la bas ... merci
J'avais pensé à une musique en utilisant "sound" de Pascal ... mais tu me surprends, donc il est tout à fait facile d'implementer une musique en mp3 ou mod avec Turbo-Pascal Borland ? si t'as des bouts de codes utilisant le MP3 ou MOD je veux bien y jeter un oeil. Curieux de voir ça. Car je ne connais pas l'utilisation des ports concernant la carte son ... pour l'instant j'ai pas vraiment la foi pour revenir en arrière et améliorer la demo mais pourquoi pas dans une prochaine .... et l'algo. pour décompresser le MP3, s'il existe en Pascal, ça m'intéresse.
Envoyé par
Alcatîz
Oh oui !
Sinon pour discutailler bouts de ... code ... voici une des parties essentielles, l'affichage des "sprites" ...
L'affichage d'une image sur l'écran consiste à copier les octets de l'image à partir d'une zone mémoire vers la mémoire de la carte graphique.
Ensuite on affiche l'image sur l'endroit d'à côté et ainsi de suite on voit une image se déplacer, ce qui est je crois un Sprite.
Je ne voulais pas de GRAPH.TPU pour l'affichage des images, pour plusieurs raisons :
- beaucoup trop lent PutImage, ça aurai été impossible de réaliser cette DEMO.
- le PutImage de GRAPH.TPU n'affiche pas du tout l'image, lorsque celle-ci ne déborde que d'un pixel en dehors de l'écran, et c'était pas acceptable pour ce que j'avais à faire
- je voulais pas de fichier externe (GRAPH.TPU), et j'avais pas besoin de toutes ses fonctionnalités
- je voulais un code éventuellement adaptable en Assembleur pour rester dans le ton d'une DEMO et l'utilisation des bibliothéques ne me plaisait pas
Ils ont pourtant pensé à tout avec PutImage et GetImage, même l'utilisation de XOR, OR, etc ... mais l'explication de la lenteur vient probablement du fait qu'ils doivent appeler leur routine PutPixel ... ce qui est naturel, mais pas optimal. Voilà la structure d'une Image EGA qu'ils ont utilisé.
Sans entrer dans les détails, une image EGA (16 couleurs) fonctionne en "BitPlan". Par exemple pour afficher une image de 640 x 350 x 16 couleurs, il faut fournir à la mémoire video de la carte graphique, 4 images monochromes d'une résolution de 640 x 350 pixels. J'entends par monochrome qu'un pixel ne peut prendre que deux états (Blanc ou Noir par exemple).
La carte graphique va en interne "superposer" ces 4 images monochromes pour afficher à l'écran une image de 16 couleurs. Principe qu'utilise par exemple l'AMIGA.
Le premier pixel de l'image monochrome BitPlan0, BitPlan1, BitPlan2 & BitPlan3 peuvent prendre 0 ou 1 donc en les associant on a par exemple :
0101 ce qui donnera pour le premier pixel visible sur l'écran une couleur de valeur 5 en décimale. Donc 4 bits on a bien 16 couleurs possibles. Il en va de même pour tous les autres pixels.
Voilà comment GRAPH.TPU stock ça dans la mémoire dynamique :
la première rangée d'octets, c'est la première ligne du bitplan3 (exemple : une image de 640 pixels de large = 80 octets puisque 8 points monochromes par octet)
la deuxième rangée d'octets, c'est la première ligne du bitplan2
la troisieme rangée d'octets, c'est la première ligne de bitplan1
la quatrieme rangée d'octets, c'est la première ligne du bitplan0
la cinquieme rangée d'octers, c'est la deuxième ligne du bitplan3
la sixieme rangée d'octers, c'est la deuxième ligne du bitplan2
la septieme rangée d'octers, c'est la deuxième ligne du bitplan1
la huitieme rangée d'octers, c'est la deuxième ligne du bitplan0
etc ...
Ce qui veut dire qu'ils fusionnent dans la mémoire de stockage de l'image, les 4 bits plans. Et comme GetImage requiert un pointeur, vous savez comme moi qu'on ne peut pas allouer plus de 65528 octets en un seul bloc.
Donc cela limite une image 16 couleurs à environ 640 x 200 (64000 octets).
Pour pouvoir utiliser le maximum de mémoire dynamique tout en restant compatible avec les fonctions de gestion mémoire fournis par l'UNIT SYSTEM, j'ai décidé de faire autrement.
C'est simple, chaque BitPlan (de 0 à 3) se réserve son bloc mémoire dynamique. Autrement dit un BitPlan (donc une image monochrome), de 60 000 octets pourrai être de 800 x 600 x 2 couleurs. Et donc les 4 bitplans forment une image de 800 x 600 x 16 couleurs, stockable en mémoire dynamique.
Donc sans être obligé de me refaire une gestion mémoire pour dépasser la limitation 64K, j'arrive en utilisant la spécificité du BitPlan à stocker par exemple un paysage de 800 x 600 x 16 couleurs.
J'utilise aussi le côté objet de Pascal ; j'instancie une classe "Image16" ce qui me donne un objet dans lequel je stock l'image. Quand je veux afficher cet objet, j'écris simplement :
1 2 3 4 5
|
paysage.disp (0, 0);
arbre.disp (30, 30);
montagne.disp (-50, 200);
etc ... |
Voici donc la classe "Image16" :
1 2 3 4 5 6 7 8 9 10 11 12
|
const planMaxSize = 60000;
var screenH, screenV : word;
type
Plan = array [0..planMaxSize-1] of byte;
Image16 = object
h, v : word;
bitmap : array [0..3] of ^Plan;
procedure disp (x, y : integer);
end; |
Les variables "screenH", "screenV" contiennent les dimensions actuelles de l'écran, car en EGA on peut passer aussi en 320 x 200.
Le type "Plan" est comme je l'explique plus haut, un tableau de 60000 octets contenant un des 4 BitPlan d'une image 16 couleurs.
"bitmap" est un tableau de 4 pointeurs. Chaque pointeur indique des données structurées façon "Plan".
"Disp" affiche l'image en copiant depuis les données "Plan" vers la mémoire de la carte graphique EGA.
Définition de la méthode "disp" :
1 2 3 4 5
|
procedure Image16.disp (x, y : integer);
var x2, y2, h2, v2, source, target, srcStep, tarStep, i : word;
begin |
x & y -> coordonnées de l'image. Type Integer volontairement choisi à la place de Word permettant à l'utilisateur de faire déborder l'image exemple : -30, -100
x2 & y2 -> coordonnées de la zone mémoire qui différent de x et y lorsque l'image à afficher déborde de l'écran ; il est inutile de lire les octets qu'on affiche pas !
h2 & v2 -> dimensions de la zone mémoire qui différent de h et v lorsque l'image à afficher déborde de l'écran ; il est inutile de lire les octets qu'on affiche pas !
autrement dit x2, y2, h2, v2 existent en plus de x, y, h, v car il y a découpage de l'image en cas de débordement ... et oui c'est jamais simple la programmation bas niveau
h2 contiendra le nombre d'octets d'une ligne découpée à copier
v2 contiendra le nombre de lignes à copier en écartant les lignes invisibles
source -> dans la zone mémoire où est stockée l'image, numéro du premier octet à lire
target -> dans la zone mémoire de la carte graphique, numéro du premier octet à écrire
srcStep -> lorsque l'on a fini de copier la première ligne de l'image, source est incrementée avec "srcStep"
tarStep -> lorsque l'on a fini de copier la première ligne de l'image, target est incrementée avec "tarStep"
n'oublions pas que srcStep et tarStep sont évidemmenent différents, car dans la zone source après une longueur de ligne, l'autre ligne se trouve juste après, alors que dans la zone graphique la ligne suivante se trouve (ScreenH / 8) octets après. Tout ça évite d'effectuer les mêmes multiplications entre chaque copie de ligne de l'image pour gratter du temps CPU.
if (x >= screenH) or (y >= screenV) or (x + h < 1) or (y + v < 1) then exit;
si l'image déborde totalement de l'écran, pas la peine de continuer plus loin puisqu'on affiche rien.
h2 := screenH - x; if h2 > h then h2 := h;
calcul de h2 : si screenH - x > largeur initiale de l'image alors pas de débordement et h2 = h sinon h2 prendra une plus petite valeure puisque découpage
v2 := screenV - y; if v2 > v then v2 := v;
calcul de v2 : même principe que pour h2
x2 := 0; if x < 0 then begin x := -((abs(x + 1) shr 3 + 1) shl 3); dec (x2, x); inc (h2, x); x := 0; end;
Calcul de x2. En cas de débordement horizontal gauche ; x := -((abs(x + 1) shr 3 + 1) shl 3); dec (x2, x); inc (h2, x); x := 0;
il n'est donc utile de lire l'image dans la zone de stockage qu'à PARTIR DE l'ENDROIT VISIBLE d'où le x := - abs (x) <-- si x = - 30, on lit à partir du 30 ème pixel.
dec (x2, x); comme x est négatif la decrementation se transforme en incrementation et x2 se trouve incrementé de 30. x = 0 car si l'image déborde à gauche, on commence par écrire les pixels à partir de 0.
shr 3 est équivalent à div / 8 ... traduit en assembleur par un pur shr 3, donc gain de temps
shl 3 est équivalent à mul * 8 ... traduit en assembleur par un pur shl 3, donc gain de temps
y2 := 0; if y < 0 then begin dec (y2, y); inc (v2, y); y := 0; end;
calcul de y2. En cas de débordement vertical haut ; même procédé que pour x2.
conversion pixels en octets exemple : 640 -> 80
par exemple si l'utilisateur veut afficher une image à partir de la position -8 horizontale. Et bien inutile de lire le premier octet, puisqu'il est invisible
1 octet = 8 points . D'où l'utilité de garder x2 et x et tous ces calculs ! il en va de même pour le vertical
1 2 3
|
srcStep := h shr 3; source := x2 shr 3 + y2 * srcStep;
tarStep := screenH shr 3; target := x shr 3 + y * tarStep; |
OUF !... toutes les valeurs sont prêtes, il n'y a plus qu'à lancer la boucle qui copie les lignes. Cette boucle sera répété 4 fois pour les bitplans de 0 à 3 ...
1 2 3 4 5 6
|
for i := 0 to 3 do begin
port [$3C5] := 1 shl i;
rowsCopy (bitmap[i]^[source], mem[40960:target], srcStep, tarStep, h2, v2);
end;
end; |
port [$3C5] := 1 shl i;
indique à la carte EGA quel BitPlan attaquer : 1, 2, 4, 8 pour dire 0, 1, 2, d'où le shl.
"disp" utilise une procedure "rowsCopy" qui copie des rangées d'octets, directement utile pour l'affichage d'une image on remarque l'utilisation de "move" et aucune multiplication à l'intérieur de la boucle, afin de gratter du temps CPU
1 2 3 4 5 6 7 8 9 10 11 12 13
|
procedure rowsCopy (var source, target : array of byte; srcStep, tarStep, rowSize, rowNumber : word);
var s, t : word;
begin
s := 0;
t := 0;
while (rowNumber > 0) do begin
move (source[s], target[t], rowSize);
inc (s, srcStep);
inc (t, tarStep);
dec (rowNumber);
end;
end; |
Il est clair qu'à le relecture de mon code, tout me parait compliqué et pourtant pour gérer les cas de débordement de l'image, ne copier que les octets utiles et qu'aucun calcul ne soit fait à l'intérieur des boucles pour un temps d'execution optimal ... il faut ce qu'il faut.
En tous cas bravo si vous avez eu le courage de tout lire et si vous avez tout compris
Partager