                           23VERY ADVANCED COPPER10


In  questo  articolo  si parlera` di tecniche avanzate di programmazione del
copper.  Lo spunto per questo articolo l'ho avuto quando mi sono accorto che
nella  lezione  11  del Corso di Asm di 2Randy1, si dice che l'istruzione 3SKIP1
del  copper  non  serve a nulla.  Io non sono di questo parere, e durante la
scrittura  del  Corso (al quale io ho collaborato scrivendo gran parte delle
lezioni  7,9, e 10), lo feci presente a Randy mediante alcuni esempi che gli
inviai affinche` li includesse nella lezione 11.  Purtroppo Randy, non lo ha
fatto,  forse  per  mancanza  di  tempo  o forse perche` ha perso nell'HD il
materiale  che  gli  avevo  inviato.   A  me  pero` non va giu` l'idea che i
giovani  coders che hanno letto il Corso (quanti sono ?) credano che la 3SKIP1
sia  inutile,  perche`  cio`  contrasta  con  uno degli Oscuri Assiomi della
filosofia 2Morbid Visions1:

 2Amiga RULEZ => Hardware Amiga perfetto => HW privo di cose inutili.1

L'3AGA1  ha  anche  dei  difetti,  ma l'3OCS1 no.  Quindi ho pensato di scrivere
questo  articolo  per  parlare  della  3SKIP1  e anche del mascheramento delle
coordinate  del  copper,  argomento  che  nel  corso  e` trattato in maniera
piuttosto  sommaria.   Vedremo, infatti che la SKIP si rivela molto utile in
alcuni  casi,  e  che  le possibilita` di mascheramanto delle coordinate del
copper  sono  molto piu` ampie di quanto spiegato, ad esempio vedremo che e`
possibile mascherare anche le posizioni orizzontali.
Le  tecniche che tratteremo in questo articolo, non vi permetteranno di fare
Ray  Tracing  in tempo reale, (altrimenti se ne sarebbe accorto anche Randy)
pero`  in  qualche  circostanza  possono  farvi risparmiare qualche preziosa
linea di raster.  E siccome nei 2Testi Oscuri Del Coding Mortale1 si asserisce
che  uno  degli  obiettivi  di  un coder e` sfruttare al 2massimo1 l'hardware,
bisogna saper sfruttare anche queste tecniche.
Iniziamo dando una descrizione piu` dettagliata del formato usato dal copper
per  le  istruzioni WAIT e SKIP.  Queste 2 istruzioni hanno un formato molto
simile, per questo faremo un'unica descrizione per entrambe.  La descrizione
del  funzionamento  della  SKIP la vedremo successivamente.  Come ben sapete
ogni istruzione copper si compone di 2 WORD.  Il formato della prima word e`
descritto nella tabella seguente:

   3PRIMA WORD ISTRUZIONI WAIT E SKIP1
     ---------------------------------
   3Bit 0           2Sempre settato a 1.

   3Bits 7 - 1      2Posizione orizzontale pennello elettronico (HP).

   3Bits 15 - 8     2Posizione verticale pennello elettronico (VP).
1
Entrambe  queste  due istruzioni basano il loro comportamento sulla verifica
di  una  certa  condizione,  che normalmente e` il superamento, da parte del
pennello  elettronico della posizione specificata dai campi di bits VP e HP.
Come  vedremo tra un attimo, e` possibile modificare tale condizione tenendo
conto   anche   dello   stato  del  blitter.   In  seguito  descriveremo  il
comportamento della SKIP.  Vediamo la seconda WORD:


   3SECONDA WORD ISTRUZIONI WAIT E SKIP1
     -----------------------------------
   3Bit 0           2Settato a 0 per la WAIT, settato a 1 per la SKIP.

   3Bits 7 - 1      2Bit maschera posizione orizzontale (HE).

   3Bits 14 - 8     2Bit maschera posizione verticale (VE).

   3Bit 15          2Blitter-finished-disable bit. Normalmente settato a 1.
1
Il bit 0 serve al copper per capire se l'istruzione in questione e` una WAIT
o  una SKIP.  I campi di bits HE e VE servono per mascherare rispettivamente
le  posizioni  orizzonate e verticale.  Il funzionamento e` il seguente:  il
copper  effettua  il  confronto tra la posizione specificata da HP e VP e la
posizione  del  pennello  elettronico  utilizzando  solo quei bit tali che i
corrispondenti  bit  di HE e VE sono settati a 1.  Se ad esempio in una WAIT
poniamo i bit di HE tutti a 1, mentre i bit 8-12 di VE a 0 e i bit 13 e 14 a
1,  il  copper  rimarra`  in attesa che il pennello elettronico raggiunga la
posizione  orizzontale  HP  (perche`  tutti  i  bit  di  HE  sono a 1) e una
posizione verticale tale che i bit 13 e 14 siano uguali ai bit 13 e 14 di VP
(perche`  essi  sono  i  soli  bit  di VE settati a 1).  Vediamo alcuni casi
notevoli.  Se vogliamo utilizzare TUTTI i bit di HP e VP (cio` non usiamo il
mascheramento),  dobbiamo  settare  a  1  tutti i bit di HE e VE.  In questo
caso,  se  abbiamo  una WAIT, otteniamo che la seconda WORD assume il valore
che   ben   conoscete  $FFFE.   Se  invece  vogliamo  una  WAIT  che  ignori
completamente  la  posizione  verticale  ma  consideri  tutti  i  bit  della
orizzontale   otteniamo   che  la  seconda  WORD  vale  $80FE,  come  accade
nell'esempio  presente  nella  lezione  11  del  corso  di Randy.  Il bit 15
permette  di  modificare  la  condizione che le 2 istruzioni verificano:  se
tale  bit  e`  settato a 1, si comporteranno normalmente, in caso contrario,
dovranno  anche  verificare  che  il  blitter  abbia terminato una eventuale
blittata  (cioe` BLTBUSY, il bit 14 di DMACONR deve valere 0).  Per esempio,
nel caso della WAIT, con il bit in questione a 0, essa oltre ad attendere il
raggiungimento  della  posizione video indicata dai bits VP e HP, attendera`
anche  la fine di un'eventuale blittata.  Cio` puo` essere utile nel caso si
vogliano  effettuare  blittate  sincronizzate  con la posizione del pennello
elettronico.

Ai  piu`  attenti non sara` sfuggito il fatto che a causa della presenza del
Blitter  Finished  Disable  bit, i bit di VE sono uno in meno dei bit di VP.
Piu`  precisamente  non  esiste  in  VE  un  bit  corrispondente al bit piu`
significativo  di  VP.   Questo  vuol  dire  che  tale  bit  (il bit 8 della
posizione verticale dello schermo) NON puo` essere mascherato.  Questo fatto
ha   delle   importanti   conseguenze.    Nelle   applicazioni,  infatti  il
mascheramento  viene utilizzato per avere delle istruzioni che si comportano
allo  stesso  modo  in  diverse posizioni di schermo.  Il fatto di non poter
mascherare  il  bit  8 della posizione verticale impedisce pertanto di avere
istruzioni  che  si comportano allo stesso modo in zone dello schermo aventi
il  bit  8  della  posizione  verticale diverso.  L'esempio tipico, mostrato
anche  da  Randy,  e`  quello  della  WAIT  che  attende una certa posizione
orizzontale indipendentemente dalla riga in cui si trova.  Se noi tentassimo
di  realizzare  una tale WAIT ponendo DC.W $00xx,$80FE otterremmo in realta`
una  WAIT  che  attende  una posizione dello schermo tale che il bit 8 della
posizione  verticale  vale  0  e  la posizione orizzontale vale xx.  Se tale
istruzione viene eseguita quando la posizione del pennello elettronico ha il
bit  8  uguale  a  0,  essa attende che il pennello elettronico raggiunga la
posizione  orizzontale  xx, come voluto.  In caso contrario, invece, poiche`
il  bit 8 della posizione verticale del pennello elettronico vale 1 e il bit
8  di  VP vale 0, la condizione della WAIT e` subito verificata, quindi tale
istruzione  NON  blocca il copper.  A causa di questo fenomeno Randy afferma
nel suo corso che il mascheramento non funziona nelle righe comprese tra $80
e  $FF.   Si tratta di una conclusione decisamente frettolosa.  Infatti, per
ottenere  l`effetto  voluto,  basta  utilizzare  una WAIT che mascheri i bit
bassi  di VP, come nel caso precedente, ma che abbia il bit non mascherabile
di  VP  posto  a  1,  ovvero  una  DC.W  $80xx,$80FE.  Tale WAIT nelle righe
comprese  tra  $80  e  $FF,  avra` il bit non mascherabile di VP allo stesso
valore  del  bit  8  della  posizione  verticale del pennello elettronico, e
pertanto  attendera` ad ogni riga la posizione orizzontale xx.  Come esempio
di  applicazione vi proponiamo nel sorgente MV_Code/Copper/mask1.s l'effetto
usato  da  Randy  per illustrare il mascheramento delle posizioni verticali,
realizzato  (diversamente  da  Randy)  nelle  righe  comprese tra $80 e $FF.
Consentiteci  a  questo  punto, rispettabili lettori di parafrasare l'inizio
dello  scrolltext  della  celeberrima INTRO 2KickReset1 di 3Razor 19111:  2"Randy
told us that this couldn't be done...nevertheless here it is!!"1 :))

Bisogna  comunque  sottlineare  che  una  WAIT mascherata con il bit 8 di VP
settato  ad  1,  se  eseguita  in  una  riga avente il bit 8 della posizione
verticale  pari  a 0, blocchera` SEMPRE il copper, in quanto il numero della
riga  e`  considerato  sempre minore della posizione specificata nella WAIT.
Cio`  significa  che  la  WAIT  del  nostro esempio ottiene l'effetto voluto
(aspettare  la  posizione orizzontale xx SOLO nelle righe comprese tra $80 e
$FF.   E se noi volessimo utilizzare le WAIT mascherate in tutto lo schermo?
Beh`,  con  un po` di lavoro in piu` e` possibile anche questo.  Il sorgente
2MV_Code/Copper/mask2.s1  e` appunto un implementazione dell'effetto visto nel
sorgente  precedente  che  funziona  in  tutto lo schermo.  Vi rimandiamo al
commento del sorgente per una descrizione delle tecniche adottate.

Veniamo  dunque  all'istruzione  3SKIP1.  Come detto, essa ha un formato molto
simile  a  quello  della  WAIT.  Il comportamento della SKIP e` il seguente:
essa  fa  saltare al copper l'istruzione seguente se il pennello elettronico
ha  superato la posizione specificata.  Per esempio consideriamo le seguenti
istruzioni:

	dc.w	$4037,$ffff	; SKIP (salta) se si supera la linea $40
ISTR1:	dc.w	$182,$0456	; istruzione move del copper
ISTR2:	dc.w	$182,$0fff	; istruzione move del copper

Quando  il  copper  esegue  l'istruzione  SKIP  controlla  dove  si trova il
pennello  elettronico.  Se esso ha superato la posizione specificata dai bit
VP  e  HP  della  SKIP  (nell'esempio  HP=$36  e  VP=$40),  il  copper salta
l'istruzione   seguente   (all'indirizzo   ISTR1)   ed  esegue  l'istruzione
successiva  ad  essa (cioe` l'istruzione all'indirizzo ISTR2).  Se invece il
pennello  elettronico  non  ha  ancora raggiunto la posizione indicata viene
eseguita  nomalmente  l'istruzione  successiva come se la SKIP non ci fosse.
Come   abbiamo   gia`  detto  anche  alla  SKIP  puo`  essere  applicato  il
mascheramento  delle  posizioni mediante i bit VE e HE della seconda WORD in
maniera analoga a quanto avviene per la WAIT; inoltre anche per la SKIP puo`
essere  azzerato  il  Blitter  Finished Disable bit, facendo si che il salto
venga  effettuato  o  meno  tenendo  conto  ANCHE  dello  stato del Blitter.
Mediante  la  3SKIP1 si possono realizzare dei loop nella copperlist.  Un loop
nella  copperlist e` un insieme di istruzioni copper che viene ripetuto fino
a  che il pennello elettronico non raggiunge una determinata posizione.  Per
realizzare  il  loop  si  usa  anche  il  registro COP2LC.  Il meccanismo e`
illustrato dal seguente esempio:

nel programma principale si esegue una

	move.l	#Copperloop,COP2LC(A5)	; scrive l'indirizzo del loop
					; nel registro COP2LC

e nella copperlist si mettono le seguenti istruzioni:

	dc.w	$2007,$FFFE	; WAIT linea $20
Copperloop:
	dc.w	$180,$F00	; istruzioni copper del loop
	dc.w	$180,$0F0
	dc.w	$180,$00F

	.
	.

	dc.w	$180,$F0F	; ultima istruzione del loop
	dc.w	$4007,$ffff	; SKIP (salta) se si supera la linea $40
	dc.w	$8a,0		; COPJMP2 salta all'inizio del loop

	dc.w	$182,$00F	; istruzione fuori dal loop

Il  funzionamento  e` molto semplice.  Dopo la linea $20,il copper entra nel
loop Dopo aver eseguito tutte le istruzioni del loop arrivera` alla SKIP.  A
questo  punto se il pennello elettronico NON ha ancora superato la linea $40
(cioe`   si  trova  piu`  in  alto  sullo  schermo)il  copper  NON  saltera`
l'istruzione  seguente.   L'istruzione  seguente,  pero`  scrive  in COPJMP2
provocando  un  salto  del  copper  all'indirizzo  scritto in COP2LC, ovvero
all'indirizzo della prima istruzione del loop.  In questo modo il loop viene
ripetuto.   Dopo  un  certo  numero  di ripetizioni, il pennello elettronico
raggiungera` la linea $40.  A questo punto quando viene eseguita di nuovo la
SKIP,  essa  fara`  saltare al copper l'istruzione che scrive in COPJMP2; in
questo  modo  esso  non  fa  piu`  il  salto all'inizio del loop ma passa ad
eseguire la prima istruzione esterna al loop.

A  cosa  servono  i  loop  nella copperlist?  E` chiaro, che possiamo sempre
farne  a  meno:   invece  di  fare  il  loop scriviamo tante volte quante ci
servono  la  parte di copperlist da ripetere.  In questo modo ci risparmiamo
la  SKIP  e l'istruzione che scrive in COPJMP2, che rallentano un pochettino
il  copper.   L'uso  dei  loop  presenta pero` dei vantaggi:  in primo luogo
risparmiamo   memoria,   perche`  scriviamo  una  volta  sola  il  pezzo  di
copperlist.   In  secondo  luogo,  se  il  pezzo di copperlist ripetuto deve
essere   modificato   dal   processore   per   realizzare  qualche  effetto,
naturalmente facendo il loop il pezzo di copperlist dovra` essere modificato
una sola volta, velocizzando moltissimo il lavoro del processore.

L'utilizzo di istruzioni WAIT all'interno dei loop presenta alcuni problemi.
Supponiamo  di  avere  un loop che si ripete dalla riga $20 alla riga $70, e
che  all'interno  del  loop ci sia una WAIT alla riga $38.  Che succede?  La
prima  volta  che il loop viene eseguito, la WAIT blocca il copper.  Dopo la
linea  $38  il  copper si sblocca, arriva alla fine del loop e lo ripete.  A
questo  punto,  siccome  il pennello elettronico ha superato la riga $38, la
WAIT  non  blocca  piu` il copper.  Come risultato, l'esecuzione della prima
iterazione  del  loop  produrra`  risultati  molto  diversi dalle iterazioni
successive.   Di  solito  questo  non e` cio` che si vuole.  Nei loop con il
copper sarebbe desiderabile poter aspettare una determinata riga del loop ad
ogni iterazione.  Per esempio si potrebbe volere qualcosa del genere:

CopperLoop:
		; istruzioni varie

		aspetta 4 righe dall'inizio dell'iterazione

		; istruzioni varie

		ripeti il loop fino ad una certa riga.

Come  si puo` realizzare un meccanismo del genere?  E` necessario utilizzare
delle WAIT con mascherati alcuni bit della posizione verticale.

Per  esempio  supponiamo  di  avere  un  loop che si estende per 16 righe di
raster  e  che vogliamo ripetere dalla riga $10 alla riga $70, ovvero per 96
righe.   Poiche` 96/16=6 il copper eseguira` 6 iterazioni.  Notate che 96 e`
divisibile  per  16  (non  c'e`  resto),  il  che  vuol dire che il pennello
elettronico raggiungera` la riga 96 esattamente nel momento in cui il copper
finisce  la  sesta  iterazione.  Vogliamo che in ogni iterazione del loop il
copper  si  blocchi  alla quarta riga a partire dall'inizio dell'iterazione.
Per   ottenere   cio`  usiamo  una  WAIT  in  cui  mascheriamo  i  bit  piu`
significativi  della posizione verticale.  In questo caso poiche` il loop si
ripete  ogni  16  righe, la WAIT si deve comportare allo stesso modo ogni 16
righe, e non deve considerare le differenze di posizione tra un gruppo di 16
righe  e  l'altro.   Quindi  e`  necessario  considerare  solo  i 4 bit meno
significativi (che formano appunto un gruppo di 16 linee).  Per mascherare i
bit  della  posizione verticale, come e` spiegato nel corso, si utilizzano i
bits  da  8  a  14  della seconda word della WAIT.  Se uno di tali bit viene
settato  a  1  (come accade di solito) il bit corrispondente della posizione
verticale  viene utilizzato; se invece uno di tali bit viene azzerato il bit
ad  esso  corrispondente e` mascherato.  Consideriamo ad esempio la seguente
istruzione WAIT:

	dc.w	$0301,$8FFE

questa  istruzione aspetta la quarta riga di un gruppo di 16 linee.  Vediamo
cosa  accade  nel  nostro esempio.  Il loop inizia alla riga $20.  Il copper
esegue  le prime istruzioni e incontra la WAIT.  Essa considera solo i 4 bit
meno  significativi  della  posizione per cui si mette ad aspettare una riga
che abbia tali 4 bit al valore $3 (infatti nella seconda WORD i bit 12,13,14
che  corrispondono ai bit 5,6 e 7 della posizione verticale sono a 0).  Cio`
accade  alla  riga  $23.   A  questo punto il copper si sblocca.  La seconda
iterazione  del  loop inizia alla riga $30.  Anche qui il copper arriva alla
WAIT  e  aspetta una riga che abbia i 4 bit meno significativi al valore $3,
cosa  che  accade  alla  riga  $33, ovvero ancora alla quarta riga del loop.
Questo  comportamento si ripete ad ogni successiva iterazione.  Se volessimo
delle  iterazioni  lunghe  8  righe  con  delle WAIT di questo tipo dovremmo
lasciare  abilitati solo i 3 bit meno significativi della posizione.  Notate
che  questa  tecnica  si  implementa  facilmente  solo se la lunghezza di un
iterazione  e`  una  potenza di 2.  Un esempio di copper loop e` il 2sorgente
MV_Code/Copper/skip1.s1.

Una  limitazione  all'uso  delle  WAIT nella maniera che abbiamo mostrato e`
dovuta  al fatto che il bit piu` significativo della posizione verticale non
e`  mascherabile.   Cio`  ci  impedisce di realizzare loop che si comportino
nella  stessa  maniera  sia  al  di  sopra  della riga $80, dove il bit piu`
significativo  vale  0, sia al di sotto, dove il bit piu` significativo vale
1,  proprio  perche`  non possiamo ignorare questa differenza mascherando il
bit.  L'unica soluzione e` di utilizzare 2 loop, uno da eseguire al di sopra
di $80 e uno al di sotto, come mostrato in 2MV_Code/Copper/skip2.s1.

Un  esempio  un po' piu` sofisticato e` nel sorgente 2MV_Code/Copper/skip3.s1.
In  tale  sorgente le copperlist sono scritte utilizzando delle 3MACRO1 invece
che  mediante  delle 2DC.W1.  Le 3MACRO1 permettono ad esempio di scrivere 2CMOVE
$0f0,COLOR001  invece che 2DC.W $180,$0f01 e, nel caso delle 2WAIT1, 2WAIT $07,$601
invece  che  2DC.W $6007,$FFFE1.  Si tratta di una scelta stilistica che a mio
avviso  rende  molto  piu`  puliti e ordinati i sorgenti.  Inoltre in questo
modo   si   evitano  molti  errori  di  "distrazione",  nella  scrittura  di
copperlist,  come  ad  esempio dimenticarsi che il bit 0 della prima word di
una 2WAIT1 DEVE essere settato a 1.  Nella prima parte dell'articolo non le ho
usate perche` volevo confondere ulteriormente le idee durante la spiegazione
della  SKIP.   Consiglio  pertanto  a tutti di utilizzarle.  Nel sorgente ci
sono delle versioni ridotte delle mie macro, che potete prendere, migliorare
e includere nei vostri sorgenti.

Naturalmente  mediante  le  SKIP  si  possono costruire loop che si ripetono
anche  all'interno  della stessa riga dello schermo.  A causa della lentezza
del  copper,  (1  istruzione=8  Pixel)  il  numero di iterazioni per riga di
solito  e`  piuttoto  ridotto.   Potete  vederne  un  esempio  nel  sorgente
2MV_Code/Copper/skip4.s1.

                                       2The Dark Coder1 / 3Morbid Visions1
