Yeni bir gün, yeni bir girdi. Bugün basit örneğimizi tamamlayacağız ve Copper'a da hafif bir giriş yapacağız. Hadi başlayalım...
Çizgimizi aşağı yukarı kaydırabilmek için, raster'ı beklediğimiz yeri dinamik olarak değiştirmemiz gerekiyor. Yani algoritmamız şu şekilde olmalı:
- Raster bekleme yerimizi ekranın en üst noktasına set edelim ($02c).
- Ana döngünün her dönüşünde raster bekleme noktamızı bir arttıralım.
- Eğer dibe ($12b) ulaşırsak bir arttırmak yerine bir azaltmaya başlayalım.
- Eğer tepeye ($02c) ulaşırsak bir azaltmak yerine bir arttırmaya başlayalım.
Dikkat ederseniz dip noktayı $12c yerine $12b aldık. Çünkü eğer $12c alırsak çizgimiz ekranın altından üstüne taşma yapıyor. Bu sorunla karşılaşmamak için bu tür bir önlem alıyoruz. Sizler $12c ile deneyip ne demek istediğimi görebilirsiniz.
Yukarıdaki algoritmamızı uygulamaya başlayalım. Öncelikle 'init:'in hemen öncesine şunları ekliyoruz:
.
.
RASTERSTART=$02c $02c is the top of the screen
RASTEREND= $12b $12c is the bottom of the screen
INCREMENT= 1 speed of the rasterline movement
init:
.
.
Gördüğünüz gibi ekran başlangıç ve bitiş satırlarını tanımladık. Satır değiştirme miktarımızı da 1 birim olarak belirledik. 'init:'in altına aşağıdaki
bold olarak işaretli satırı ekliyoruz. Satır değiştirme miktarımızı d6 data register'ine koyduk -- Bir register daha harcadık, bu iş böyle olmaz

.
.
init:
move #RASTERSTART,d7 d7 initialized to $0ff
move #INCREMENT,d6 d6 initialized to 1
.
.
Şimdi, çizgiyi çizdiğimiz noktada, çizmeden hemen önce, çizginin bulunduğu yere göre arttırım veya eksiltme işlemini yapacağız. Arka plan rengini siyaha çektiğimiz yerin hemen altına aşağıdaki bold olarak işaretlenmiş kodu ekliyoruz:
.
.
move.w #$000,COLOR00 move color black to COLOR00(background palette)
add d6,d7 add the INCREMENT to raster position
cmp.w #RASTEREND,d7 raster at the bottom?
blo .a not yet so jump over next line
neg d6 passing the screen bottom, need to return,
* so negate the INCREMENT
.a:
cmp.w #RASTERSTART,d7 raster at top?
bhi .b not yet so jump over next line
neg d6 passing the screen top, need to return,
* so negate the INCREMENT
.b:
waitraster1:
.
.
Eklediğimiz kısmı incelersek, bizim için yeni birkaç komut göreceğiz. O yüzden bu kısmı yine satır satır açıklayalım:
add d6,d7: Adı üstünde toplama işlemi bu. Soldaki yerdeki değeri sağdaki yere ekliyor ve sonuç yine sağdaki değerin içinde kalıyor. Burada birşey belirtmediğimiz için word işlemi yapılıyor. Zaten d6'yı ve d7'yi initialize ederken de INCREMENT ve RASTERSTART değerlerimizi değerimizi word olarak atamıştık. Dolayısı ile burada word toplama yapmak doğru olacak.
cmp.w #RASTEREND,d7: Bu komutu biliyoruz. d7'nin içinde $12b değeri mi var diye kontrol ediyoruz. Yani d7'yi arttıra arttıra gidiyoruz, ekranın dibine vardık mı acaba?
blo .a: 'blo', 'Branch if Lower' demek. Yeni bir komut ama gayet anlaşılır ne yaptığı. Bir önceki karşılaştırmamızda sağdaki değer soldaki değerden küçükse verilen label'a dallanır. Burada dikkat ederseniz label'imizin solunda bir '.' var. Bu nokta, bu label'in bir lokal label olduğunu gösteriyor. Eğer bu nokta yoksa o label 'global' bir label'dır. Aradaki fark basitçe şu: Lokal label'lar iki global label arasında tanınır. Yani bir lokal label'ı tanımladığınız ismi, arasında kaldığı iki global label dışında tekrar kullanabilirsiniz. Kodlamayı kolaylaştırmak için getirilmiş muhteşem yöntemlerden biri.
neg d6: Buraya geldiyse üstteki dallanma çalışmadı demektir. Yani karşılaştırma sonrası d7, ekranın dip noktasına eşit (ya da büyük) çıktı. Bir arttırma yerine bir azaltma yapmaya başlamamız lazım. 'neg' komutu tam olarak bunu yapıyor. d6 içindeki sayı herneyse onu -1 ile çarpar yani 'negate' eder. Artık d6 içinde -INCREMENT var yani -1.
.a: Bir lokal label. Öğrendik biz bunu.
cmp.w #RASTERSTART,d7: d7'yi bir de ekranın tepe noktası ile karşılaştıralım. Tamam dipten çıkmadık ama belkide tepeden uçmak üzereyiz.
bhi .b: 'bhi', 'Branch if Higher' demek. Yani bir önceki karşılaştırmamızda sağdaki değer soldaki değerden büyükse verilen label'a dallanır. 'blo' gibi çalışıyor, çok farklı değil.
neg d6: Buraya geldiysek d6 içinde eksi değer var ve bizde tepeye vurduk. Bir 'negate' çekelim de tekrar yola girelim. Artık d6 içinde -(-INCREMENT) var yani 1.
.b: Hmmm.
Bir çalıştıralım bakalım, ne olacak:
Not a valid vimeo URL
E, gayet güzel kayıyor işte. Başardık!. Yanlız ekranda bir pislik, arkada kalan görüntüler var vs. Ekranı silsek, tertemiz olsa daha güzel olacak. Hadi bunu da Copper kullarak yapalım, böylece copper'a da yumuşak bir giriş yapmış oluruz.
Copper nedir, ne değildir? Şuradan (
link) okuyabilirsiniz. Aslında basitçe CPU üzerinden yük almak için tasarlanmış, custom bir yan-prosesördür. Değerli CPU zamanı yerine, görece değersiz olan copper zamanını kullanarak CPU'da daha önemli işlerimizi paralelde yaptırabiliriz. Copper, her biri 4-byte yani 2-word yani bir longword uzunluğunda olan komutları sırayla okuyup çalıştırır. Geleneksel olarak bu komut listesine 'CopperList' denir. Copperlist, Amiga'nın Chip memory'si içerisinde ve assembler tarafından data olarak tanımlanan bir section'a yerleştirilmek zorundadır. Burada section'dan kasıt bir memory aralığından başka birşey değil.
'Birşeyi örnekle öğrenmek en iyi yöntemdir' düstürümüzden hareketle, ekranı temizleme işini copper'a yaptıracağız. Ama öncelikle ne yapmamız gerekiyor? Amiga coding'in özünü unutmayın: Hiçbir şey kolay değil, hiçbir şey acı çekmeden olmaz. Önce acımızı çekmemiz lazım. Epeyce bir hazırlık, tanımlama vs yapmamız gerekiyor copper'ı ayağa kaldırmak için.
Copperlist'i yerleştireceğimiz data section'umuzu tanımlamamız lazım ama Assembler'a data bölümünü belirtmek için önce kod bölümünü belirtmek gerekiyor. Data section biraz daha beklemek zorunda. Kodumuzun en üstüne şu satırı ekliyoruz:
SECTION rasterline_code,CODE_C
.
.
Assembler'a diyoruz ki:Bir SECTION daha görene kadar bundan sonraki tüm kodu CODE section'unun içine yerleştir. Hatta bu section mutlaka Chip memory içinde bir yerlerde olsun. Bunu CODE_C'nin sonundaki C ile belirtiyoruz. C yerine F de yazabilir veya hiçbirşey yazmayabilirdik. F, Fast memory'i ifade eder. Eğer sadece CODE dersek, Amiga kafasına göre bir yerlere (Public Memory) yerleştirecek demektir.
Kodumuzun en alt kısmına şu satırı ekliyoruz:
.
.
rts
gfxname:
dc.b "graphics.library",0 name of the library to open
Burada bir tanımlama yaptık. 'dc.b' yani 'declare bytes' komutunu kullanarak 'graphics.library' string'ini bir memory alanına yerleştirdik. Sonuna da bir NULL karakter koyduk (,0). Yani toplamda 17 bytelık bir yer ayırdık, içine 'graphics.library\0' yazdık. Bunun ilk byte'ının adresini de 'gfxname:' label'ı gösterecek şekilde ayarladık. Yani kodumuz içinde bir yerde 'gfxname'in gösterdiği adrese atıfta bulunursak, 'g' karakterini içeren bir byte'a atıfta bulunuyor olacağız. 'gfxname+1'in gösterdiği yere atıfta bulunursak, 'r' karakterini içeren bir byte'a atıfta bulunacağız. Bu şekilde...
Şimdi bu yeni değişkenimizi kullanalım. 'init:' satırının hemen sonrasına aşağıdaki parçayı ekleyelim:
.
.
init:
move.l 4.w,a6 execbase
move.l #gfxname,a1 filling in the paramter for
jsr -408(a6) calling oldopenlibrary*()
move.l d0,a1 getting return value
move.l 38(a1),d4 getting the original copper ptr
jsr -414(a6) calling closelibrary()
move #RASTERSTART,d7 d7 initialized to $0ff
.
.
'Yavaş codewarrior, serin gel' dediğinizi duyar gibiyim. Merak etmeyin açıklayabilirim, bir fırsat verin, vurmayın..
Yukarda bir yerlerde demiştik ya, copper copperlist'i işler durur diye. İşte normalde AmigaOS'un işlettiği bir copperlist'i işliyordu bu ana kadar. Biz araya girip, copper'a da çöküp AmigaOS'un elinden alıp kendi emellerimize alet edeğiz. Yanlız arabayı çizdirmeden sahibine geri vermek gibi ulvi bir amacımız var unutmayın. Photon'un deyimi ile 'kötü çocuklar değiliz biz'. Sağlam vereceksek de orjinal copperlist adresini bir yerlere saklamamız lazım ki, çıkarken geri yerine koyabilelim. İşte bu adres ulaşabilmek için 'graphics.library'i açıp içinden bir fonksiyonu çalıştırıp, onun döndürdüğü değerler arasından copperlist pointer'ı alıp saklayacağız. Sonra da tabii 'graphics.library'i kapatsak fena olmaz. Boşuna memory harcamasına gerek yok. İşte bu kod parçacığı tam olarak bu işi yapıyor. Bir adresi almak için ne eziyetler, ne eziyetler.
Gene satır satır gidelim:
move.l 4.w,a6: AmigaOS içerisindeki çalıştırılabilir rutinlerin bir tablosunu a6 isimli adres registerına yüklüyoruz. Adres registerları, data registerları gibi çalışırlar. Onlar gibi longword bir değer tutarlar ve yazıp-okuyabilirsiniz. Ama tuttukları değerler data yerine adres değerleridir. Adresleme işlemlerinde kullanılmak üzere bazı güzel ek özellikler barındırırlar. Bir tanesini sonraki satırlarda göreceğiz. Buradaki komut, entresan bir şekilde 4. memory bölgesi ile işlem yapıyor. Dikkat ederseniz 4'ün başında bir # yok. Dolayısı ile bu bir adres değeri ve resmen Amiga'daki hafızanın 4.noktasına direk bir atıf yapıyor. Bu nokta OS içindeki çalıştırılabilir rutinlerin tablosunun adresini içeriyor aslında. Burada word olarak bu adresi a6 içine alıyoruz.
move.l #gfxname,a1: Hatırlarsanız 'gfxname' bir label ve bir string'e işaret ediyor. Daha önce anlattığım gibi eğer biz 'gfxname'e direk atıfta bulunursak bu string'e ulaşıyoruz. Eğer '#gfxname' şeklinde yazarsak etiketin numarasına yani tahmin edebileceğiniz gibi 'adres'ine ulaşıyoruz. Bu satırla 'gfxname' label'inin gösterdiği string'in başlangıç adresini a1 adres register'ına alıyoruz.
jsr -408(a6): 'jsr', 'Jump to Subroutine' demek. Burada, a6 adres registerinin gösterdiği byte'ın 408 byte gerisinden başlayan bir subroutine'e yani fonksiyona atlıyoruz. Bu noktada uygulamamız o adresteki o fonksiyonu çağırıyor ve dönene kadar o fonksiyon çalışıyor. Bu fonksiyon 'oldopenlibrary' fonksiyonu. Bir kütüphane açmamızı ve içindeki rutinlere ulaşmamızı sağlıyor. Peki hangi kütüphane? Hangisi olduğunu a1'in içine koyduğumuz adres ile söyledik ya: 'graphic.library'. 'oldopenlibrary' fonksiyonunun detayı için şuraya bakabilirsiniz:
Link. Burada adres registerlarının güzel bir özelliğini görüyoruz: -408(a6). Bir adres register'ını parantez içine alıp öncesine sayısal bir değer verirsek, adres registerının gösterdiği noktayı geçici olarak (bu satır için sadece) o sayısal değer kadar değiştirebiliriz.
move.l d0,a1: Fonksiyon çalıştı ve döndü. Sonucu 'd0' içine bıraktı. Dikkat ederseniz 'oldopenlibrary' fonksiyonu parametresini a1 içinden aldı, sonucunun (ki epey büyük bir data) başlangıç adresini d0'a bıraktı. Bu her fonksiyon için farklıdır. Bu satır ile d0 içindeki adresi olduğu gibi a1 içine aldık.
move.l 38(a1),d4: AmigaOS'un kullandığı orjinal copperlist pointer a1 registerinin gösterdiği değerin 38 byte ilerisinde. Bu noktadan bir longword okuyup d4 registeri içine yazıyoruz. Şükürler olsun, orjinal copperlist adresini elde ettik.
jsr -414(a6): a6'nın içinde hala AmigaOS içerisindeki çalıştırılabilir rutinlerin tablosu duruyordu. Başka bir fonksiyon çağırıyoruz: CloseLibrary, detaylar burada:
Link. Hangi library'i kapatacağını parametre olarak aldığı a1 içisindeki aldığımız dataya bakarak anlıyor. Yani bu fonksiyon 'gfxname' ile gösterilen ismi kullanmıyor. Zaten dikkat ettiyseniz, a1'in içinde o ismin adresi yok artık. Böylece 'graphics.library'i kapattık gitti.
Orjinal copperlist adresi var artık elimizde ve d4'ün içinde. Uygulama sonuna kadar d4'e de dokunamayacağız artık. Kötü programlama nedir, nasıl yapılır? Örneklerle gösteriyorum burada.

İleride düzelteceğiz bunları hep, merak etmeyin.
Artık sıra geldi copperlistimizi yazmaya. Öncelikle copper ne yapabilir, fonksiyonelitesi nedir, onu bir anlamaya çalışalım. Copper, özetle sadece üç çeşit işlem yapabilir: MOVE, WAIT ve SKIP.
MOVE:: $reg,$değ: Bir Amiga donanim registerine ($reg) bir word değer ($değ) yükler. Genel olarak copper'ın erişebildiği tüm registerlerin adresleri $dffxxx şeklindedir. Tüm bu adresler $dff ile başladıkları için copperlistte bu kısmı yazmaya gerek yoktur ve son 3 basamak bir word şeklinde yazılır: $xxx.
WAIT:: $SATIRSUTUN,$fffe: Raster'ın belli bir pozisyona gelmesi beklemek için kullanılır. Soldaki word değerin ilk byte'ı raster'ın satır pozisyonunu, ikinci byte'ı sütun pozisyonunu gösterir. İkinci word değer ise birinci word'deki bitlerin hangilerinin karşılaştırmaya katılıp katılmayacağını gösterir. Normalde 'tüm bitler' anlamına gelen $fffe kullanılır. Diyelim ki siz sadece satır kontrolü yapmak istiyorsunuz. O zaman $ff00 yeterli olacaktır. Bu durumda birinci word'deki ikinci byte (SUTUN) hesaba katılmayacaktır. WAIT komutu olduğunu belirtmek için ikinci word'ün birinci biti daima 0'dır.
SKIP: $SATIRSUTUN,$ffff: Raster'ın belli bir pozisyona gelene kadar sonraki copperlist komut satırı atlanır. Çalışma mantığı WAIT ile birebir aynıdır. Sadece SKIP komutu olduğunu belirtmek için ikinci word'ün birinci biti daima 1'dir.
Copperlist her zaman $ffff,$fffe komutu ile biter. Gördüğünüz gibi bu aslında, 255.satır 243.sütunu bekle komutudur. Böyle bir satır-sütun olmadığı için bu hiç gerçekleşmeyecek bir komuttur ve copperlist sonu anlamına gelir. Copperlist komutları genelde tek satıra iki word şeklinde tanımlanır ama isterseniz 4 byte veya tek bir longword şeklinde de tanımlayabilirsiniz.
Bu örnekteki copperlistimizi yazmak için uygulamamızın en altına şu satırları ekliyoruz:
.
.
gfxname:
dc.b "graphics.library",0 name of the library to open
SECTION rasterline_data,DATA_C
Copper:
dc.w $100,$0200 disable all bitplanes
dc.w $ffff,$fffe end of the copperlist
.
.
Satır satır gidelim gene.
SECTION rasterline_data,DATA_C: Copperlist mutlaka Chip memory içinde ve bir data section'unda yer almalı. Dolayısı ile 'rasterline_data' isminde böyle bir section tanımladık.
Copper: Copperlist'imizin başlangıç adresini gösteren label.
dc.w $100,$0200: 'declare words' komutunu kullanarak 2 word tanımlıyoruz. Bu bir MOVE işlemi. $dff100 adresine $0200 word değerini atıyor. Bu 'Bit Plane Kontrol Register 0'ın adresi. Buraya $0200 değerini yükleyerek tüm 'bit plane'leri kapatıyoruz. Bunun detayına daha sonra geleceğiz ama bu noktada şunu bilmeniz yeterli. Tüm bitplane'leri kapatmak ekranda halihazırda var olan görüntüleri silecektir. Yani ekranı temizliyoruz bu şekilde.
dc.w $ffff,$fffe: Bunu biliyoruz. Copperlist bitiş komutu.
Artık bir copperlistimiz olduğuna göre bunu orjinal copperlistin yerine koyabilir ve bunun çalışmasını sağlayabiliriz. Bunun için aşağıdaki kodu start'ın altına ekliyoruz ki copperlist'imizi yazacağımız register adresini aklımızda tutmak zorunda kalmayalım:
.
.
INTENAW= $dff09a Interrupt enable bits (clear/set bits)
COPPERADDR= $dff080 Copperlist address
RASTERSTART=$02c $02c is the top of the screen
.
.
Ve copperlistimizin adresini copper registerine yazıyoruz. 'init:' içine şunu ekleyelim:
.
.
move #$7fff,INTENAW disable all Interrupts
move.l #copper,COPPERADDR putting our copperlist in
mainloop:
.
.
Çilemiz henüz bitmedi. Arabayı sahibine teslim edeceğiz unutmayalım. Tahmin edeceğiniz üzere çıkmadan hemen önce orjinal copperlist'i yerine koymalıyız ki AmigaOS bir şok geçirmesin. d4'ü hiç kullanmadık bu arada, yani orjinal copperlist adresi hala orada olmalı. Aşağıdaki kodu 'exit:'in hemen sonrasına ekliyoruz:
.
.
exit:
move.l d4,COPPERADDR put the original copperlist back
.
.
Evet, tamamız galiba. Bir deneyelim bakalım, nasıl oldu?:
Not a valid vimeo URL
Soğuk bir birayı hakkettik bence.

Selamlar, sevgiler.
Not: Sonraki episode'da CPU üzerindeki yükü Copper'a taşıyoruz ve rasterline'ın kalınlığını arttırıyoruz. Öğrendiklerimizle bunu yapmanın ne kadar kolay olacağına şaşıracaksınız.