Demos Amiga

Démos en assembleur sur Amiga 500-600-1200

Client

Psykotrope

Année

1990-1996

Catégorie

CTO

Partager

🚀 Mission

Pousser les performances des ordinateurs Amiga des années 90 à fond, défier les limites d’un microprocesseur 680x0

👍 Résultat

Réalisation de démos avec des effets graphiques codés en Assembler 680x0 et de la musique tonitruante réalisée sur des trackers. Elles ont été présentées à différentes Party en France, au Danemark, …

Contact

📝 Notes

À l’époque, seuls les militaires et les universitaires avaient internet. Le HTML n’existait pas encore ! Pour trouver de l’info, en France on était super bien logés, on allait sur le minitel sur 3615 RTEL. C’était le point de rendez-vous de tous les geeks. Les codeurs des pays nordique payaient une fortune pour avoir le minitel chez eux et pour se connecter en France sur les BBS dédiés à l’informatique. ET mes parents hurlaient pour que je lâche le minitel qui représentait un coût de communication exorbitant à leurs yeux 😀

Souvenirs souvenirs

Quelques heures avant la deadline de la compétition à laquelle nous avions présenté Eternity et Shortcut to Heaven, deux intros de 64ko (voir les vidéos ci-dessous).

Images Portfolio Psykotrope2

Voici quelques réalisations récupérées chez des Allemands nostalgiques

Jean-Paul 2

C’était une intro de moins de 40Ko avec les dessins, font, musique, … Elle utilisait le nouveau mode AGA des Amiga qui permettait d’afficher jusqu’à 16777216 couleurs, wouah !

Elle a été présentée à la party Sun is a Pac Man et a fini en 2ème place.

The 4k Intro

C’était une intro de moins de 4Ko avec les dessins, font, musique, … Oui, Quatre Kilo Octets ! Gros défis de compression, économie de chaque octet. Pour faire tourner le cube par exemple, on a besoin de faire des calculs de rotation et donc on a besoin des fonctions de sinus et de cosinus. Les CPU 680x0 n’avaient pas de calcul à virgule flottante. Au lancement de la démo, on générait les 90 premiers degrés de la fonction sinus avec un calcul de série entière et on recopiant ensuite dans la RAM par symétrie les 90 degrés suivant et encore par symétrie, de 180 à 360 degrés. Pourquoi ? Parce que la taille du code de ma série entière prenait 88 octets, soit 2 de moins les 90 octets des 90 premiers degrés de la fonction sinus.

Ce cube ne fait pas grand-chose mais à l’époque, c’était une révolution car il a 3 effets :

  • En plus de la rotation, il y a la profondeur qui induit des calculs lourds en plus. Une division pour chaque point en gros. Pour vous ce n’est rien, mais pour un CPU 680x0 à l’époque, ça voulait dire beaucoup 😂
  • Les faces changent de couleur en fonction de leur orientation pour donner un effet plus réel (3 calculs en plus car seules 3 faces sont visibles à chaque instant dans un cube). On ne pourrait pas être à 25 images par seconde en faisant ce calcul. On le pré-calcule donc avant que le cube soit affiché et quand on affiche le cube, on a juste à aller lire dans la RAM quelle est la couleur à afficher pour chaque face en fonction des rotations X, Y et Z. Malin non ?
  • Le cube est transparent. Impossible à calculer à l’époque dans un temps raisonnable. Pour simuler cette transparence, j’utilisais une astuce propre à la gestion des couleurs par le chipset graphique. Au lieu d’utiliser les 256 variations de couleur pour chaque composante Rouge, Vert et Bleu, je n’en utilisais que 128 et j’utilisais le bit restant pour afficher la partie du décor situé derrière. C’est une astuce parce que le décor derrière a plusieurs couleur alors que sous le cube, il n’y en a que 2 mais l’œil n’y voit que du feu

Concernant la musique, c’est dommage, l’allemand qui a ripé la vidéo de cette intro ne l’a pas mise. Mais le concept était le même, le sample qui composait la musique était calculé au lancement ! C’était la première 4ko intro avec de la musique à l’époque.

Today is sunday

Encore une intro de moins de 40Ko présentée au Danemark à la Party 4. Elle a été disqualifiée parce qu’elle faisait planter le video-projecteur de l’organisation. Pour accélérer le nombre d’images par seconde de mon labyrinthe mappé, je passais l’affichage du mode PAL au mode NTSC, car j’avais découvert que le CPU s’overclockait en faisant ça et ça me permettait d’avoir le premier labyrinthe mappé à la VBL, c’est à dire au top du top à l’époque : 25 images par seconde !

VBL, ça veut dire Vertical Blank Line, c’est le temps que met l’électron dans le tube cathodique pour parcourir tout l’écran, d’en haut à gauche jusqu’à en bas à droite. Et oui pas de LCD à l’époque ! Si le temps de calcul de la prochaine image à afficher est inférieur à la VBL, il suffit d’attendre le temps restant pour switcher l’image (double buffer) et on obtient 25 images par seconde.

Si le temps de calcul de la prochaine image est supérieur à la VBL par contre et qu’on switch immédiatement le double buffer, on obtient alors une animation légèrement plus lente mais avec de gros artefacts qui représentent le mélange de l’image précédente avec la nouvelle. Beurk 🤮 A l’époque, on appelait les codeurs qui osaient faire ça des Lamers !

Shortcut to Heaven

Sur cette démo, je n’ai rien codé, c’est Psy qui s’en est chargé.

24h avant la deadline de présentation des démos, il me dit qu’il n’a pas de musique et me demande d’en composer une. Sympa le challenge. Je lui ai donc composé la musique pendant la Saturne Party 2 à Paris après 48h de débogage de ma démo Eternity sans dormir. On était dans une salle de 2000 geeks venus de toute l’Europe, dans un brouhaha pas possible. Ce n’est qu’après la Party, au calme, que je me suis rendu compte qu’il y a un passage dans la musique qui fait un peu “fausse note” 😬

Petite explication technique des démos de l’époque : vous voyez la photo du lapin tout seul ? Pourquoi le codeur s’amuse-t-il à nous montrer une image ? Juste pour faire beau ? Non évidemment :-) Pendant que vous regardez l’image, il est en train de précalculer toutes les rotations de l’image en RAM pour pouvoir s’en servir ensuite pendant l’effet zoomrotat où il n’a plus qu’à calculer le zoom. Malin n’est-ce pas ?

Eternity

Cette démo s’appelle Eternity parce que sont des cubes en rotation qui défilent pendant… une éternité !

L’allemand qui a rippé la vidéo a eu un petit problème, dans la vraie vie, il n’y avait pas de noir et blanc… Techniquement, j’avais travaillé sur l’effet d’apparition en cercle qu’on voit au début à l’intérieur des petits carrés et tout à la fin pendant les crédits.

Pendant cet effet au début sur le texte, je pré-calcule tous les cubes comme un malade en RAM pour pouvoir les afficher tranquillement ensuite dans la démo.

Toute la scène avait l’habitude dans ses démos de raconter des choses sans intérêt et de remercier d’autres personnes d’autres groupes. Si quelqu’un en Europe ne le faisait pas dans une démo, il était sifflé pendant sa présentation par plusieurs milliers de personnes.

Nouveau détail technique aussi : pour synchroniser la musique avec les effets, on mettait des codes dans la musique. Quand on lit la musique dans la routine principale, dès qu’on détecte le code caché, on déclenche la fin d’un effet, le début d’un nouveau, …

Spayce Pack

C’est quoi un Pack ? C’était une disquette avec plein de démos dessus que les groupes s’échangeaient. Zone (le graphiste du groupe) s’occupait de spreader nos démos partout en Europe pour qu’on soit connus. Dans chaque groupe, un membre était spreader, c’était le marketing de l’époque avec les moyens qu’on avait.

Zone a pas mal gagné en notoriété et par la force des choses, on a créé nos packs qui ont cartonnés : les Spayce Pack. La vidéo que vous voyez au dessus est donc un lanceur de démo. Ça peut paraître simple comme ça, mais toute la difficulté consiste à lancer une démo et quand celle-ci est finie, récupérer l’ordinateur pour retourner dans le Spayce Pack. Une démo utilise tout dans l’ordinateur :

  • le boot
  • le CPU comme si celui-ci était nous propre au lancement de la machine
  • toute la RAM (comment je fais moi pour stocker l’état d’exécution du Spayce Pack si la démo que je lance utilise tout ?)

A y penser aujourd’hui, à l’heure de VMWare, de Docker, des machines virtuelles Erlang, … j’avais pragmatiquement utilisé les mêmes concepts sans que le CPU 680x0 ne m’aide en quoique ce soit.

Hidden parts

Dans toutes les démos (même la 4ko Intro), le spectateur peut déclencher une partie secrète en faisant un Konami Code like. Avis aux possesseurs d’Amiga :-)

Bonus pour les barbus

Voici le code Assembler 680x0 que j’ai écrit à l’époque pour mapper une texture sur un mur composé de 2 triangles dans le labyrinthe de Today is sunday. La base d’un moteur 3D, c’est le triangle !

Routine3D.24anim.s

incdir
include --sources:bases/base.s
Planewidth  =   320/8
Planelong   =   256
Planesize   =   Planelong*Planewidth
Plan    =   4
;***********************************************************************
A_Start:    lea DemoGo(pc),a0
    move.l  a0,$80
    trap    #0
    rts

;---------------OUVRE LA GFXLIBRARY
;------------------------------------------------------------------------
DemoGo: move.l  execbase,a6
    lea     gfxname,a1
    jsr OpenLibrary(a6)
    move.l  d0,gfxbase

;---------------ALLOUE LA MEMOIRE
;------------------------------------------------------------------------
    move.l  Execbase,a6
    move.l  #planesize*plan,d0
    move.l  #Clear,d1
    jsr AllocMem(a6)
    move.l  d0,Planeadr
    beq.w   fin

;---------------INITIALISE LA COPPERLIST
;-----------------------------------------------------------------------
    lea Copperlist+2(pc),a0
    move.l  Planeadr,d0
    moveq   #plan-1,d1
copperloop:
    swap    d0
    move.w  d0,(a0)
    swap    d0
    move.w  d0,4(a0)
    addq    #8,a0
    add.l   #planesize,d0
    dbf d1,copperloop

;---------------PAS DE MULTITACHE
;-----------------------------------------------------------------------
    lea $dff000,a5
    move.l  Execbase,a6
    jsr     Forbid(a6)

;---------------INSTALLE LA COPPERLIST
;-----------------------------------------------------------------------
    move    #$03e0,DMAcon(a5)
    move.l  #Copperlist,Cop1lch(a5)
    clr Copjmp1(a5)

;---------------DONNEES DE L'ECRAN
;-----------------------------------------------------------------------
    lea $dff000,a5
    move.w  intenar(a5),old_intena
    move.w  #$2871,Diwstrt(a5)
    move.w  #$28d1,Diwstop(a5)
    move.w  #$0038,Ddfstrt(a5)
    move.w  #$00a0,Ddfstop(a5)
    move.w  #%0100000000000000,bplcon0(a5)
    move.w  #$0,Bplcon1(a5)
    move.w  #$0,Bplcon2(a5)
    move.w  #$0,Bpl1mod(a5)
    move.w  #$0,Bpl2mod(a5)

;---------------INSTALLE LA VBL ET LES INTERRUPTIONS
;-----------------------------------------------------------------------
    DMASET= %1000010111001111
    move.w  #dmaset!$8200,dmacon(a5)
    lea $dff000,a5
    move.l  $6c,Old_vbl
    lea new_vbl,a0
    move.w  #$7fff,intena(a5)
    move.l  a0,$6c
    move.w  #$c020,intena(a5)
    move.w  #3,$dff1fc
    move.w  #0,$dff106
    move.l  #$0123088f,$dff180
    moveq   #0,d0
    moveq   #0,d1
    moveq   #0,d2
    moveq   #0,d3
    moveq   #0,d4
    moveq   #0,d5
    moveq   #0,d6
    moveq   #0,d7

    move.w  #256-1,d0
    moveq   #0,d1
    lea YTable,a0
Loopm1:
    move.w  d1,(a0)+
    addi    #40,d1
    dbf d0,loopm1
;***********************************************************************

    move.l  planeadr(pc),a0
    lea pts,a2
    moveq   #2-1,d0
EveryObject:
    move.l  a2,a1
    bsr.w   FillTri
    lea 12(a2),a2
    dbf d0,EveryObject

mousy:  btst    #6,$bfe001
    bne.b   mousy
;***********************************************************************
fin:    btst    #14,$dff002
    bne.b   fin
    lea $dff000,a5
    move.l  execbase,a6
    move.l  Old_vbl,$6c
    move.w  old_intena,d0
    or.w    #$8000,d0
    move.w  d0,intena(a5)
    move.l  gfxbase,a4
    move.l  Startlist(a4),Cop1lch(a5)
    clr Copjmp1(a5)
    move.w  #$8020,DMAcon(a5)
    jsr Permit(a6)
    move.l  gfxbase,a1
    jsr CloseLibrary(a6)

    move.l  execbase,a6
    move.l  planeadr,a1
    move.l  #planesize*plan,d0
    jsr FreeMem(a6)

    moveq   #0,d0
    rte

;************************************************************************
cop:        dc.l    0
MyCopper:   dc.l    0
Planeadr:   dc.l    0
Gfxbase:    dc.l    0
old_intena: dc.l    0
old_vbl:    dc.l    0
Irq_flag:   dc.l    0
cladr:      dc.l    0
Gfxname:    dc.b    "graphics.library",0
    even

;---------------COPPER LIST DATA
;------------------------------------------------------------------------
Copperlist: dc.w    $e0,$0
    dc.w    $e2,$0
    dc.w    $e4,$0
    dc.w    $e6,$0
    dc.w    $e8,$0
    dc.w    $ea,$0
    dc.w    $ec,$0
    dc.w    $ee,$0
    dc.w    $ffff,$fffe
FinCopperlist:

;---------------WAIT VERTICAL BLANKER
;------------------------------------------------------------------------
New_vbl:    move.b  #$ff,Irq_flag
    move.w  #$20,$dff09c
    rte
WaitVbl:    clr.b   Irq_flag
loop:   tst.b   Irq_flag
    beq.s   loop
    rts


;------------------------------------------------------------------------
; ROUTINE DE REMPLISSAGE DE TRIANGLE !     CODED BY POTSKY
; Entree:   a0:destination     a1:ptr sur les points
;------------------------------------------------------------------------

FillTri:    movem.l d0-d7/a2-a6,-(sp)

;----------------------------------------
; TRI DES 3 POINTS SELON Y
;----------------------------------------
    lea ptorg,a2
    movem.w (a1)+,d0-d5
Tri1:   cmp d3,d1       ; Tri Par Y des pt du triangle!
    ble.b   Tri2        ;
    exg d3,d1       ; Y
    exg d2,d0       ; X
tri2:   cmp d5,d1
    ble.b   Tri3
    exg d5,d1       ; Y
    exg d4,d0       ; X
tri3:   cmp d5,d3
    ble.b   tri4
    exg d5,d3       ; Y
    exg d4,d2       ; X
tri4:   move.w  d2,(a2) ; on save nur X2 pour la suite

;----------------------------------------
; CALCUL DES COEFFS DIRECTEUR
;----------------------------------------
    move.w  d0,a5       ; save x,y
    move.w  d1,a6
    sub d2,d0       ; DX1 petite 1ere
    sub d3,d1       ; DY1
    sub d4,d2       ; DX2 petite 2eme
    sub d5,d3       ; DY2
    sub a5,d4       ; DX3 grande
    sub a6,d5       ; DY3

    ext.l   d0
    ext.l   d1
    beq.b   d1nul
    swap    d0      ; 1er petit coef
    divs.l  d1,d0
    move.l  d0,a2
    bra.b   Cont.1

D1nul:
    moveq   #0,d0
    move.l  d0,a2

Cont.1:
    ext.l   d2
    ext.l   d3
    beq.b   d3nul
    swap    d2      ; 2eme petit coef
    divs.l  d3,d2
    move.l  d2,a3
    bra.b   Cont.2
D3nul:
    moveq   #0,d2
    move.l  d2,a3
Cont.2:
    ext.l   d4
    ext.l   d5
    beq.b   D5Nul
    swap    d4      ; 3eme big coef
    divs.l  d5,d4
    move.l  d4,a4
    bra.b   cont.3
D5nul:
    moveq   #0,d4
    move.l  d4,a4
Cont.3:

;----------------------------------------
; FILL DE LA 1ERE PARTIE DU TRIANGLE
;----------------------------------------
    lea Ytable,a1           ; Avant le test du scan 2 !!!
    move.w  (a1,a6.w*2),a6  ; offset y de d�part

    tst.l   d1
    beq.b   scan2init
    bpl.b   Scan_Y1
    neg.l   d1
Scan_Y1:    movem.l d2/d3,-(sp)
    subq    #1,d1       ; dbf rulez !
    moveq   #0,d0
    moveq   #0,d2       ; virflo.1
    moveq   #0,d3
    moveq   #0,d6       ; virflo.2
    moveq   #0,d7

Scan_Y2:    swap    d2      ; 1ere line
    move.w  d2,d3
    add.w   a5,d3
    swap    d2

    swap    d6      ; 3eme line
    move.w  d6,d7
    add.w   a5,d7
    swap    d6

    move.w  d7,d0    ; save d7 !!!
    sub d3,d0        ; difference !
    beq.b   NoFill
    bpl.b   Scan_OK1 ; Ok !!!!!!
    neg d0           ; valeur abs !
    exg d3,d7        ; scan de d3 vers d7

Scan_OK1:   divu    #31,d0      ; 108
    beq.b   onlyFewPix
    subq    #1,d0
ScanLoop:   bfset   (a0,a6){d3:31}
    addi    #31,d3
    dbf d0,ScanLoop
OnlyFewPix: clr.w   d0
    swap    d0
    beq.b   NoFill
    bfset   (a0,a6){d3:d0}

Nofill: addi    #40,a6
    add.l   a2,d2
    add.l   a4,d6
    dbf d1,scan_Y2
    movem.l (sp)+,d2/d3
    bra.b   fin1erscan


scan2init:  moveq   #0,d6

;----------------------------------------
; FILL DE LA 2EME PARTIE DU TRIANGLE
;----------------------------------------
Fin1erscan: lea ptorg,a1
    move.w  (a1),a1     ; perpendiculaire du new pt
    tst.l   d3
    beq.b   Fin2emescan
    bpl.b   Scan_Y1b
    neg.l   d3

Scan_Y1b:   subq    #1,d3  ; dbf rulez !
    moveq   #0,d0          ; virflo.1
    moveq   #0,d1
    moveq   #0,d2          ; sinon c crade !

Scan_Y2b:   swap    d0      ; 2ere line
    move.w  d0,d1
    add.w   a1,d1
    swap    d0

    swap    d6      ; 3eme line
    move.w  d6,d7
    add.w   a5,d7
    swap    d6

    move.w  d7,d2     ; save d7 !!!
    sub d1,d2         ; difference !
    beq.b   NoFillb
    bpl.b   Scan_OK1b ; Ok !!!!!!
    neg d2            ; valeur abs !
    exg d1,d7         ; scan de d1 vers d7

Scan_OK1b:
    divu    #31,d2      ; 108
    beq.b   onlyFewPixb
    subq    #1,d2
ScanLoopb:
    bfset   (a0,a6){d1:31}
    addi    #31,d1
    dbf d2,ScanLoopb
OnlyFewPixb:
    clr.w   d2
    swap    d2
    beq.b   NoFillb
    bfset   (a0,a6){d1:d2}

Nofillb:    addi    #40,a6
    add.l   a3,d0
    add.l   a4,d6
    dbf     d3,scan_Y2b
Fin2emescan:
    movem.l (sp)+,d0-d7/a2-a6
    rts

;************************************************************************
Pts:
    dc.w    10,10
    dc.w    10,100
    dc.w    20,100


    dc.w    10,10
    dc.w    20,10
    dc.w    20,100

PtOrg:
    dc.w    0
YTAble:
    dcb.w   256

Vous avez un projet ?

Contactez-nous

Nous pouvons être opérationnels d'ici quelques jours 🚀

Contact