VBXE + Action! Animowane sprite’y.

Choć w zasadzie powinienem napisać jeden sprite 🙂

set 14=$8000
set $491=$8000
include "H1:RUNTIME.ACT"

MODULE
int fno=[1], i=[0]
byte bltst=$D653
byte bltmv0=$A115, bltmv1=$A116, bltmv2=$A117

byte array bltsrc =
   [ 0 96 1
   128 96 1
     0 97 1
   128 97 1 ]

proc CIOVE=$E4DF(byte areg, xreg)

proc delay()
 card i
 for i=0 to 30000 do od
 return

proc bltme()
 if fno mod 10 = 0 then
  bltmv0=bltsrc(i*3)
  bltmv1=bltsrc(i*3+1)
  bltmv2=bltsrc(i*3+2)
  bltst=1 fno==+1 i==+1
  if i > 3 then
   fno=1 i=0
  fi
 [$4C $E462]
 fi
 fno==+1
[$4C $E462]

proc main()
 byte vc=$D640, clock=$14
 int i byte memcont=$D65E, membank=$D65F
 byte psel=$D645, csel=$D644, cr=$D646, cg=$D647, cb=$D648
 byte xdla0=$D641, xdla1=$D642, xdla2=$D643
 byte blt0=$D650, blt1=$D651, blt2=$D652 ; bltst=$D653
 byte bkg=710, curinh=752
 byte iocb1cmd=850
 card iocb1buf=852, iocb1len=856
 byte nmien=$D40E
 card vvblkd=$0224

 byte array xdl=[98 136 219 0 0 0 64 1 17 223]

 byte array clrscr=
   [ 0 0 0     ;src addr
       0 0     ;src step y
         0     ;src step x
     0 0 0     ;dst addr ($0000 up)
      64 1     ;dst step y (320)
         1     ;dst step x
      63 1     ;width (320-1)
       239     ;height (240-1)
         0     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 byte array bltmv=
  [ 0 96 1     ;src addr
       0 2     ;src step y
         1     ;src step x
  224 62 0     ;dst addr
      64 1     ;dst step y
         1     ;dst step x
     124 0     ;width
        53     ;height
       255     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 if vc<>$10 then
  printe("Brak VBXE FX")
  delay()
  [$4C $C2AA] ;reset
 fi

 memcont=$A8
 membank=128+20
 moveblock($A000, xdl, 10)
 xdla0=0 xdla1=64 xdla2=1

 moveblock($A100, clrscr, 21)
 blt0=0 blt1=65 blt2=1
 bltst=1 ;blitter's clear scr
 while bltst <> 0 do od
 moveblock($A115, bltmv, 21)
 blt0=21 blt1=65 blt2=1

 graphics(0)
 bkg=0 curinh=1 put(31)

 close(1)
 open(1,"D1:HEL.PAL",4,0)
 psel=1 csel=0

 for i=0 to 255 do
  cr=getd(1) cg=getd(1) cb=getd(1)
 od
 close(1)

 open(1,"D1:HEL.PIC",4,0)
 iocb1cmd=7
 iocb1buf=$A000
 iocb1len=$1000

 for i=22 to 29 do
  membank=128+i
  CIOVE(0,$10)
 od

 close(1)
 vc=3
 membank=128+20

 i=clock while clock=i do od
 nmien=0
 vvblkd=bltme
 nmien=$40
 do od
return

Reklamy

VBI finescroll + DLI rainbow (ASM)

Od piątku leżę w szpitalnym łóżku (spokojnie, to tylko kolejne badania moich szwankujących mięśni) i pomimo tego, że sobotę i niedzielę spędziłem z dzieciakami w domu (przypomina mi się seria „Wielkie ucieczki” na TVN), trochę mi się nudzi. Dzisiaj mnie biopsnęli w nogę, więc może trochę pod wpływem anestetyków postanowiłem dokończyć mój ulubiony (ad nauseam) scroll poziomy w VBI i „rainbow effect” w DLI, tym razem w assemblerze i tym razem w lewo.

hscrol = 54276
sdmctl = $022F
dlist = 560
nmien = $D40E
vvblkd = $0224
xitvbv = $E462
scount = $8000
colt = $D017
indx = $8002
wsync = $D40A
vdslist = $0200

      org $4000

init  ldy #0
      sty sdmctl                                ; wyłącz antic
      lda <ndl                                  ; ustaw adres tablicy ndl jako dliste
      sta dlist
      lda >ndl
      sta dlist+1
      lda #16
      sta scount                                ; wyzeruj liczniki
      sty chrno
      sty indx
      lda #64
      sta 88
      lda #156
      sta 89

      ; vbi+dli
      lda <scroll                               ; ustaw scroll w opóźnionym vblank interrupt
      sta vvblkd
      lda >scroll
      sta vvblkd+1
      lda <dli                                  ; ustaw adres procedury dli
      sta vdslist
      lda >dli
      sta vdslist+1
      lda #42
      sta sdmctl                                ; włącz antic
      lda #$c0                                  ; włącz przerwania dli
      sta nmien
      ; kolory i napisy
      lda #0
      sta 710
      posxy #2, #5
      putline #txt1
      lda #50
      sta 88
      lda #155
      sta 89
      posxy #0, #0
      putline #txt2

loop  jmp loop

scroll ldy scount
       dey
       sty hscrol
       beq @+
       sty scount
       jmp xitvbv
@      ldy chrno
       iny
       cpy #30
       bne chmem
       lda #25
       ldx #0
       stx chrno
       sta ndl[28],x
       lda #155
       sta ndl[29],x
       ldx #16
       stx scount
       jmp xitvbv
chmem
       clc
       sty chrno
       ldx #0
       lda ndl[28],x
       adc #2
       sta ndl[28],x
       lda ndl[29],x
       adc #0
       sta ndl[29],x
       ldy #15
       sty hscrol
       iny
       sty scount
       jmp xitvbv

dli    pha
       txa
       pha
       tya
       pha
       inc indx
       ldx #7
@      lda #1
       sta wsync
       lda indx
       cmp #30
       beq @+
       dex
       cpx #0
       beq ret
       clc
       adc vcount
       sta colt
       jmp @-
@      ldy #0
       sty indx
       jmp @-1
ret    pla
       tay
       pla
       tax
       pla
       rti

scount .by 0
chrno .by 0

.array ndl [33] .byte
 112, 112, 112, 66, 64, 156, 2, 2, 2, 2, 6, 2, 2
 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 130, 86, 30, 155
 65, 32, 156
.enda

txt1  .by 'test finescroll!' $9b
txt2  .by 'mads compiler!' $9b

.link 'putline.obx'
.link 'posxy.obx'

a2

Powyższy przykład przesuwa druga z linii w trybie Antic 6/GR. 1, ale sporo zabawy zajęło mi uzyskanie prawidłowego finescroll w Antic 2 / GR. 0. Z tego co udało mi się wyeksperymentować, w trybie GR. 0 jeden piksel potrzebuje pół cyklu koloru, skoro tych pikseli jest 8, do przesunięcia jednego znaku potrzeba 4 zmiany rejestru finescroll (1 cykl koloru = 1 finescroll). Analogicznie w trybie GR. 1 / Antic 6, jeden piksel potrzebuje jeden cykl koloru, znak ma 8 pikseli szerokości, więc mamy finescroll o 8 per znak.

Warto wspomnieć o jeszcze jednym zjawisku. W przykładach w Action! i assemblerze scroll działał prawidłowo, ale tylko w prawo i tylko o 16 punktów. Wydawało mi się to dziwne i długo nie dawało spokoju dlaczego tak się dzieje, że przy finescroll o 8 widać coś jakby szarpnięcie o jeden znak. Zjawisko brało się stąd, że zmieniałem adres pamięci w display liście i wychodziłem z procedury, a zmiana rejestru hscroll na odpowiadający nowemu położeniu znaków na ekranie, następowała dopiero przy następnym vblank interrupt (czyli hscroll był nadal 0, a ANTIC przez chwilę do następnego vbi wyświetlał znak przesunięty po zmianie adresu). Zmiana adresu pamięci i jednoczesne ustawienie hscroll przed jmp xitvbv usunęło problem i wszystko działa tak jak w książce…

Reasumując, Mads jest moim ulubionym cross-assemblerem i pewne jego cechy i funkcje bardzo mnie zachęcają do dalszych eksperymentów, jednak coraz większy widzę sens w programowaniu w Action! Przede wszystkim czytelność kodu jest o wiele większa i na pierdoły nie traci się wiele czasu (tutaj ten sam efekt w Action!). Ciekaw jestem jak sprawdzi się Action! + VBXE.

PS: putline i posxy pochodzą z LIBRARIES/stdio/lib mads. Możnaby jeszcze wyzerować pamięć obrazu, ale… X /C w SDX zrobi to za mnie ;).

Action! Jedno RUNTIME, wiele modułów.

To już chyba ostatni wpis  z „linkowaniem” za pomocą type lub cat, który jednak pokaże do czego może się przydać Symbol Table Lister.  Załóżmy, że sklejamy kilka kawałków/modułów w Action! Fajnie byłoby aby Runtime było dołączane tylko raz – wymyśliłem sobie, że skoro STL pokaże mi adresy procedur w Runtime, to można zrobić tak:

  • Uruchomić w monitorze R: „H1:STL.ACT”
  • Otworzyć w edytorze Ctrl+Shift+R H1:RUNTIME.ACT i dodać np. set 14=$8000 set $491=$8000
  • Przejść do monitora Ctrl+Shift+M, skompilować C, zapisać W „H1:RUNTIME.OBX”

W LISTING.TXT (patrz poprzedni wpis o STL) znajdziemy adresy:

PrintE……… $822C PROC(BYTE ARRAY)
PrintF……… $8430 PROC(BYTE ARRAY, CARD, CARD, CARD, CARD, CARD)

Więc w naszym programie możemy zdefiniować:

set 14=$8739
set $491=$8739

proc printf=$8430(byte array a, card b, card c,
                  card d, card e, card f)
proc printe=$822C(byte array a)

proc main()
   printf("%S%E", "Test")
   printe("Runtime")
   do od

Kompilujemy i zapisujemy np. jako TESTRT.OBX
Potem sklejamy cat RUNTIME.OBX TESTRT.OBX > TESTRT.XEX

Dzięki temu każdy z dołączanych kawałków binarnych będzie mniejszy, a dodatkowo kompilacja będzie trwać krócej, bo RT kompilowane będzie tylko raz. Szkoda, że kompilatora uruchomionego w emulatorze nie da się odpalać w skrypcie. Można by do tego napisać makefile albo skrypt w bashu…

Loader do linkowanych binarek Action!

Loader do plików binarnych z Action! Wiki nie działa z binarkami z kompilatora Action! łączonymi cat lub type. Sprawdziłem w dis6502 i wynika to z tego, że loader ładuje segmenty do pierwszego wystąpienia INITAD ($02E2), a w naszym pliku binarnym utworzonym przez cat lub type, INITAD jest definiowany kilka razy (w każdej z binarek składowych). W poprzednim przykładzie z FINAL.XEX interesuje nas drugi INITAD, przekazujemy więc 2 jako parametr procedury Load. BTW nie wiem czy da się wyłączyć w kompilatorze dodawanie INITAD, ale loader może sobie to zliczać.

Poniżej lekko zmieniona wersja loadera, która czyta bloki do wartości parametru initadn.


MODULE ;LOAD.ACT
BYTE CIO_status
CARD start, len

CHAR FUNC CIO=*(BYTE dev, CARD addr,size, BYTE cmd,aux1,aux2)
[$29$F$85$A0$86$A1$A$A$A$A$AA$A5$A5$9D$342$A5$A3$9D$348$A5$A4$9D$349
$A5$A6$F0$8$9D$34A$A5$A7$9D$34B$98$9D$345$A5$A1$9D$344$20$E456
$8C CIO_status$C0$88$D0$6$98$A4$A0$99 EOF$60]

CARD FUNC ReadBlock=*(BYTE dev, CARD addr, size)
[$48$A9$7$85$A5$A9$0$85$A6$A5$A3$5$A4$D0$6$85$A0$85$A1$68$60$68
$20 CIO$BD$348$85$A0$BD$349$85$A1$60]

CARD FUNC GetOne()
  BYTE cLow
  CARD cHigh
  DO
    cLow=GetD(1) cHigh=GetD(1)
    cHigh== LSH 8 % cLow
  UNTIL cHigh#$FFFF OD
RETURN(cHigh)

PROC GetAddrs=*()
  start=GetOne()
  len=GetOne()-start+1
RETURN

PROC Load(CHAR ARRAY filespec, BYTE initadn)
  CARD INITAD=$2E2
  INT tmp=[0]
  Close(1)
  Open(1,filespec,4,0)
  WHILE tmp#initadn DO
    IF start=$2E2 THEN
      tmp==+1
    FI
    GetAddrs()
    ReadBlock(1,start,len)
  OD
  Close(1)
  [$6C INITAD]
RETURN

PROC MAIN()
 Load("H1:FINAL.XEX", 2)
RETURN

Mała uwaga: loader kompiluje się z alternatywnym runtime Jeffa Reistera. Z oryginalnym z OSS wywala error 11.

Finescroll w VBI + tęcza w DLI

Zabawy z przesuwaniem tekstu chyba nie będzie końca. Tym razem do przesuwu typu finescroll realizowanego w ramach VBLANK interrupt dodamy ruchomą tęczę w ramach procedury obsługi przerwania DLI.

int i=[0], j=[0], k=[0]
byte wsync=$D40A, vcount=$D40B
byte colt=$D017, indx=[0], hscrol=$D404
card pointer pc
card tmp
include "H1:DEFINES.ACT"

proc dli()
[pha txa pha tya pha]
indx==+1
for i=0 to 7 do
 wsync=1
 if indx>30 then indx=0 fi
 colt=vcount+indx
od
[pla tay pla tax pla rti]

proc scroll()
 hscrol=j
 j==+1
 if j=17 then
  j=0 pc^==-2 k==+1
  if k=14 then
   pc^=tmp k=0
  fi
 fi
[jmp xitvbv]

proc main()
int i card savmsc=88
byte clock=$14
byte nmien=$D40E
byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 130 86 216 159 65 32 156]
card dlist=560
card vvblkd=$0224
byte col0=708
byte col1=709
card vdslist=$0200
graphics(0)
dlist=ndl
savmsc=40000
col0=14 col1=14

for i=1 to 23 do
 printf("line: %i%E", i)
od
print("action!")

pc=dlist+28
tmp=pc^+6
i=clock
while clock=i do od
nmien=0
vvblkd=scroll
vdslist=dli
nmien=$C0
do od
return

a w pliku DEFINES.ACT:

define rti="$40",
pha="$48", pla="$68", txa="$8A",
tax="$AA", tya="$98", tay="$A8",
jmp="$4C", php="$08", plp="$28",
xitvbv="$E462" 

Najwięcej czasu (ze dwa niespokojne tygodnie :)) zajęło mi dojście do tego, dlaczego w dli muszę zmieniać sprzętowy rejestr koloru, a nie jego cień :). Wynika to z faktu, że jeżeli zmienię rejestr cień, to wartości rejestrów cieni są przepisywane do rejestrów sprzętowych GTIA dopiero w systemowej procedurze przerwania VBLANK, czyli po wyświetleniu ramki do końca.  Skutek jest taki, że wszystkie linie ekranu malowane są ostatnim kolorem z pętli „wsync=1… colt=”  w dli. Po zakończeniu procedury dli() w rejestrze cieniu siedzi sobie tenże ostatni kolor z pętli, bo kolor zmieniony w rejestrze cieniu nie trafia od razu do GTIA, układ graficzny maluje sobie dalej kolorem przepisanym podczas poprzedniego VBLANK. Kończy ramkę, wywołuje przerwanie VBLANK, przepisuje rejestr sprzętowy ostatnią wartością  rejestru systemowego i wszystkie linie od pierwszej do tej, dla której dli jest ustawione maluje w tym samym kolorze.

Jeżeli ustawimy rejestr sprzętowy ($D017 zamiast $2C5) to przy wsync=1 nowy kolor jest przepisywany do GTIA w momencie przerwania poziomego, następna linia do końca jest malowana tym kolorem, tak osiem razy bo w tej linii trybowej mamy osiem linii w wierszu, kończy się malowanie ramki, w VBLANK system operacyjny przepisuje rejestry sprzętowe wartościami z rejestrów cieni, czyli maluje od pierwszej linii trybowej, do tej w której ustawione jest dli kolorami z cieni. Potem startuje dli, zmienia rejestr sprzętowy osiem razy maluje w innym kolorze itd, itd.

Muszę jeszcze tylko przetestować binarkę na prawdziwym Atari… (update: działa).

Ciekawe ile jeszcze takich fascynujących „odkryć” na mojej drodze :). Pewnie sporo.

Action!: Finescroll w VBLKD.

Poprzednie przykłady z „Poradnika programisty” brzydko się rwały przy przesuwaniu, zwłaszcza na prawdziwym Atari. Na podstawie różnych „odkryć i znalezisk” powstał poniższy przykład w Action! wykorzystujący Vertical Blank Interrupt. A tutaj analogiczny przykład, który robi to samo w zwykłej pętli i synchronizuje się z VBLANK interrupt za pomocą zegara systemowego.

int j=[0], k=[0]
byte hscrol=54276
card pointer pc
card tmp

include "H1:RUNTIME.ACT"

proc scroll()
 hscrol=j
 j==+1
 if j=17 then
  j=0 pc^==-2 k==+1
  if k=14 then
   pc^=tmp k=0
  fi
 fi
[$4C $E462] ;jmp xitvbv

proc main()
 int i card savmsc=88
 byte clock=$14
 byte nmien=$D40E
 byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
                   2 2 2 2 2 2 2 2 2 2 2 86 216 159 65 32 156]
 card dlist=560
 card vvblkd=$0224
 byte col0=708
 byte col1=709

 graphics(0)
 dlist=ndl
 savmsc=40000
 col0=14 col1=14

 for i=1 to 23 do
  printf("line: %i%E", i)
 od

 print("Action!")

 pc=dlist+28
 tmp=pc^+6
 i=clock
 while clock=i do od
 nmien=0
 vvblkd=scroll
 nmien=$40
 do od
return

scroll() dodałem jako część opóźnioną procedury obsługi przerwania. Wg Tajemnic Atari powinno się ustawiać vvblkd po „cyknięciu” zegara, żeby się nie okazało, że nie starczy cykli procesora na ustawienie adresu naszej procedury obsługi przerwania. Tak też zrobiłem. Nie jestem przekonany czy to jest konieczne.

Finescroll.

Czasem moje eksperymenty przenoszą się na godziny późno-nocne 🙂 Tym razem przemyślenia w temacie „finescroll poziomy”. Synchronizacja nie powinna uciekać, bo przed przesunięciem łapiemy ramkę sprawdzając czy zegar „tyknął”, a dzieje się tak podczas VBLANK interrupt 50 razy na sekundę. W tym wpisie analogiczny przykład z VBI, przy okazji którego pojawił się pomysł uzupełnienia tej procedury o i=time while time=i do od.


include "H1:RUNTIME.ACT"

proc main()
 int i=[0], j=[0], k=[0]
 card dlist=560
 byte hscrol=54276
 card pointer pc
 card savmsc=88
 card tmp
 byte col0=708
 byte col1=709
 byte time=$14

 byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
                   2 2 2 2 2 2 2 2 2 2 2 86 216 159 65 32 156]
;                  64 + 6 (GR.1) + 16 (hscrol on) = 86
 graphics(0)
 savmsc=40000
 dlist=ndl
 col0=14
 col1=14

 for i=1 to 23 do
 printf("line: %i%E", i)
 od

 print("Action!")

 pc=dlist+28
 tmp=pc^+6
 do
  i=time while time=i do od
  hscrol=j
  j==+1
  if j=17 then
   j=0 pc^==-2 k==+1
   if k=14 then
    pc^=tmp k=0
   fi
  fi
od
return