LORD OF THE DEMOS - BÖLÜM I

- Haydi bakalım neymiş bu: ===========================

Once upon a time, diye başlayalım... Burda çok basit de olsa ufak bi yazı dizisi ile az çok basit bi demo framework tarzı bişi nasıl hazırlanır ondan bahsetmeye çalışacağım. Burda anlatacağım şeyin, yüzyirmisekiz byte, dört k v.b tarzı demolara pek apply edilemeyeceğini söyleyebilirim. Daha çok bahsetmek istediğim şey, yapısal olarak biraz da flexible bi design nasıl yaparız onun üstüne değineceğim.

Öncelikle bir design'a başlamadan önce temel yapı taşlarımıza bakalım. Neler lazım bize sonuçta. Düşünelim bakalım. Demo dediğimiz şeyi standart bir oyun application v.s. gibi şeylerden ayıran tarafı, user interaction kısmının hemen hemen hiç olmaması. Yani 'escape' dışındaki bir tuşu kontrol eden bir demo türüne henüz rastlamadım (çok enderdir, yok space'e basarsın secret music gelir falan filan...) Bu yüzden o açıdan epey bir şanslıyız. Yani user interaction yoksa her şey daha bi kontrol altında olur. Aslında temel yapacağımız şey bir müzik/ ses v.s. eşliğinde sunacağımız görüntüsel effectler zinciri tarzında, o anki yaptığımız makinada yapılması gerçekten zor olup ve görsel olarak da sanatsal bi tarafı olan bi yapıdır. Yani aslında... Film çekiyoruz... Başlar ve bir süre sonra da biter. Hiç bir şey farklılık göstermez. Çok enteresan şekillerde hazırlanmış bariz randomize effectler dışında her çalıştırdığımızda aynı saliselerde aynı görüntüleri görür.. aynı sesleri duyarız. O zaman burdan şuna gelebiliriz ki, en sağlam olması gereken yerimiz, bizim timing control'umuz diyebiliriz. Yani istediğimiz şeyleri, istediğimiz anlarda yapmamızı sağlayan bir yapı. Bunu öyle bir design etmeliyiz ki, ufacık bir değişiklik yapmak bize pahalıya patlamamalı.

Tamam timing olayı bir yana, bunun dışında neler gerekiyor. Temel bi gfx lib tasarımı !? ...Hmm tam olarak değil. Çünkü neticede 'standard'ın dışında bir şeyler yapacağımız için bize uygun bir library oluşturulabilinir mi bilemiyorum. Tıpkı AI library'leri gibi düşünün. Her oyunda kullanılabilecek bir AI library'si yazmak mümkün mü !? Hayır değil. Neticede AI dediğimiz şey, fake'lerden ibaret, oyununa göre yapacağın fake, veya izleyeceğin yöntem değişir. Ancak tabi ki bazı belli başlı yapılar var... Örneğin bir AI için bakarsak, ne bileyim ben bir path finding önemli yapı taşlarından biri. (Haaala anlamış değilim niye path finding'i AI sayarlar... o ayrı konu). Yani yapacağımız şeyin içine bunu koyabiliriz.. Ama general bişi yapmak imkansız... Aynısı demogfx lib'i için de geçerli. Ne koyabiliriz ki içine... Çok basit ve genel şeyleri !? Nedir bu... Bir grup tridi objeyi çok rahat bi şekilde ekrana istediğimiz shader'la basabilecek bir yapı !? Yoksa procedural texture hazırlayıp texture'u ekrana basan bi yapı !? Yoksa 3-5 pass'de hazırlanmış 3-5 texture'u alıp onlara screen space effect ile post-processing yapan bi yapı!? Yoksa hepsi mi !? Yani yok bunun tam cevabı. Mükemmelini de yapmak imkansız. İki gün sonra aklınıza "yaa hadi burda da bezierlerden 4. derece fractal bişii çizdirip arkasında da radiosity hodo hodosu" yapayım dediğiniz anda o library'den tek bir function çağırmıyor durumunda bulacaksınız kendinizi. Ama, bi çok şeyi kolaylaştırabilmemizi sağlayan bi yapı yapmak elbette mümkün. Buna değineceğiz.

Başka neyimiz var bunların yanı sıra??.. Müzik!.. Evet müzik veya ses effectleri. Burda yapılacak abartı miktarda bir şey yok açıkçası. Genel olarak ses effect'leri çok kullanılmaz. Yani ya direk müziğin içine gömülüdür o effectler ya da... Yoktur.. O yüzden demo'muz başladığı andan itibaren sadece 'play' diyip, arkasından her şeyi kendi haline bırakmak her zaman en güzel çözüm gibidir.

Temel bi bakışta bu üç component yeterli gibi gözükse de (timing/gfx/music) bunların yanı sıra arka planda olup da bizim işimizi cidden çok feci anlamda kolaylaştıran bi kaç yapı daha vardır. Bunların arasında, memory manager, resource manager (texture, sound, mesh..vs. gibi şeylerin yüklenme mekanizması ve demo tarafından erişebilirliği..) ve en önemlisi thread manager library'si. Biliyorsunuz artık günümüzde bir adet CPU diye bir şey kalmadı. Bir adet CPU bile olsa sistemde bir de GPU diye bi kavram var ki bunlardan maximum performansı elde etmek istiyorsak bunların senkronizasyonunu çok çok çok iyi bi şekilde sağlamak zorundayız. ki... Günümüzde hemen hemen bi çok kişide artık ya dual-pentium ya da en azından hyper-thread pentium ayarında aletler var. Kalkıp da CPU'nun %50'sini kullanan ve GPU ile sync olmayan (yani GPU çalışırken CPU bekler veya CPU çalışırken GPU bekler) bi yapı hazırladığımızda makinadan alacağımız performans kafadan üçte birine düşer. İşte buradaki sync olayları için kesinlikle bir thread manager, task manager vari bir yapı kesinlikle şarttır. Bi taraftan CPU'nun biri bi procedural texture'u hazırlarken (ki.. aslında GPU'da da mümkün) diğer taraftan diğer CPU mesh'leri sort ederken GPU'da bi önceki frame'i present etmek için, bi önceki frame'in sortedilmiş mesh'lerini çizebiliyor olmalı. Bunu çok rahat bir ortamda sağlayamadığımız bir durumda ortalık 1 - çöplük gibi olur, 2 - ciddi anlamda bir performans akışı sağlayamayız.

- Tamam güzeldir, nedir yani şimdi bu bahsettiklerimiz: ========================================================

Özetle şöyle basit bir şekil çizelim ascii'miz ile:

thread/task manager resource manager | |--> audio render timer -----> demo flow -- demo core engine -| (AV layer) | |--> video render memory manager

Her bi component aslında kendi başına bile kitap olabilecek kadar detaylı. Fakat demo durumunda bu detayları mümkün olduğunca atabiliriz. Ve işimize yarayan kadarı üstüne konuşup bi design çıkartabiliriz. Hangisinden anlatmaya başlayacağım ben de bilmiyom ama, şöyle basitçe bi timer'dan girelim bakalım nerelere gidiyor durum. Tabi bunu design ederken her zaman diğer component'ları gözümüzün önünde bulundurmalıyız ki, sonuçta çıkaracağımız design düzgün bir yapı olsun ve istediğimiz değişikliği parmak şıklatana kadar yapabilelim.

- Let the time flow: ====================

Evet timer. GetTickCount(), GetPerformanceCounter(), Gettime(), ... Bunlar boş şeyler. Yani önemli olan time'i nasıl aldığımız değil. Hiç de bir önemi yok. İki gün sonra function'ın adı belki de "GiveMeTheTimeMothaFucker()" olabilir. Dediğim gibi hiç bi önemi yok. Önemli olan burada bu time olayını nasıl olup da sisteme entegre edeceğiz. Bunun üstüne değineceğiz. Demonun akışı tamamen time'a bağlıdır. Belli bir zaman geldiğinde belli şeyler yapılması istenilir. Bunun design'ını yaparken bir başka istediğimiz husus, rahat debug edebilmek. Yani belki de istiyoruz ki bütün demo... Aslında yavaş çalışsın. Normal zamanın ne bileyim ben 9.3562 de birinde. Çünkü o zaman belki de görülmeyen bir bug'ı yakalayabiliriz.

Aynı yapı oyunlarda da çok sık kullanılır. Rahat debug edebilme, 'constant' fps sağlamak, gerektiğinde başka bi virtual timer ile değiştirmek, kafadan 40. saniyeden başlatabilmek... v.s. timeline gibi düşünün. nasıl ki ben 35.ci saniyeden film playlesin diyorsam demo'da da bunu diyebilmeliyim. ve her şey ona göre düzgün bir şekilde akmalı.

Tabi bütün bunların dışında en önemli sorun (ki bi çok kişinin dikkat etmediği mevzu...) Constant frame rate. Nasıl olur da sağlanır. ? Bıraktım demoyu bugün bi çok profesyonel oyun bile adam gibi sağlayabilmiş durumda değil bunu. Bi pixel ilerletirsin adamı/arabayı/ yaratığı... frame rate 75fps'den 35'e düşer. ... "u-uh.. No!.. Bad design".. Eee napacağız !? Hah çözüm bulduk!.. Bütün olayı 35 fps'ye setleyelim o zaman. O zaman düşmez. Yani bütün demo'ya bakalım en düşük kaç fps oluyor ona bakalım. Ona setleriz bütün akışı.. Yooo dostum yooo zaman cpu o 75fps yapabildiği yerde boş kalıyor mu olacak ?! Yok böyle bişii."unused system resource" exception fırlatılır suratına! Madem ki 2 cpu 1 gpu'muz var dibine kadar kullanıp. Mümkün olduğunca da constant frame rate nasıl sağlanır. bunu kasmak gerekmektedir. (bu arada design temelini 2 cpu 1 gpu muş gibi varsayıyorum. Şu an için sizde böyle bi sistem olmasa da bundan sonra böyle olacak bu iş.. Hatta yakında 4/8 cpu 2/4 gpu olması an meselesi. (sli cards, multiple cpu boards...etc.. Fiyatları her geçen gün düşüyor)

O zaman nasıl yaklaşmalıyız olaya. Oyunlarda çok sık kullanılan bir yöntemden bahsetmek istiyorum burda. Bu yöntemin bir dezavantajı var. Her şey totalde bir frame geriden takip ediyor. Yani şu an hesaplanan bir frame var fakat şu an çizilmekte (audio/video) olan frame aslında bi önceki frame. Basit bi delay özetle. Oyunda insanlar bunun kurtardığını söylüyor (50 fps hesabımız varsa 20 ms lik bi delayden bahsediyoruz).. Demo da hayli hayli kurtarır. Ama flexible ve constant time control çok önemli bi konu.

İlk vermemiz gereken karar demomuzun kaç fps olacağına karar vermek. "eee abicim varsın gitsin 1000 fps... süper işte baksana öküz gibi hızlı..." yav kime ne canım kardeşim 1000 fps ne... Aynı frame'i 1000 kere hesaplayıp basmanın kime ne faydası var""olur mu ya bak 760 fps idi... 1000 fps'ye çıkardım..." boş laflar bunlar. Basit bi filme bakalım sinemada gördüğümüz bir film 24 fps. televizyonda gördüğümüz sahneler 25 fps'dir. (50fps ama interlaced) bunun böyle yapılmasının sebebi insan gözünün onun ötesinde ki frame'leri aslında kaçırıyor olması. Yani arka arkaya akan 30 frame'e bi saniye de bakarsanız aslında göz biyolojik sebeplerden bu frame'lerin 6-7 tanesini kaçırır. Orda öyle bi resim gördüğünüzü bile bilmezsiniz. O yüzden bu fps'yi insanın nasıl algıladığına yakın bir şekilde seçmek kesinlikle makine açısından ve düzgün resource kullanımı açısından en güzel seçim olur. Oyunlarda bu biraz daha yüksek tutulur.. Örneğin 50-60fps oyun için yeterli bi fps'dir. Normal film fps'sinden biraz daha yüksek olmasının sebebi ortada bi user-interaction'in olmasından kaynaklanıyor. Yani tam frame'in başında bir hareket yaptığınız anda onun etkisini 40ms sonra ekranda görmek biraz delay'li olabiliyor (25fps = 40ms per frame) ama 20ms'lik bi delay açıkçası çok da belli olmuyor.. (yani 50 fps) ama başta da dediğimiz gibi demo olayında user interaction yok. Her şey kontrolümüz altında her türlü fake'e açığız. O yüzden birazcık daha düşük fps hedeflemek açıkçası bize koymaz.

Bi ikinci husus bu hedef fps yi seçerken, (hani demiştim ya diğer componentlara da bakmak lazım) gfx lib'imizi de göz önünde tutmak lazım. Ekranımızın tarama frekansı da çok önemli. Yani ekran update hızımızın monitor'un refresh rate'inin rational bi katı olması çok önemli örneğin 60hz tarayan bi monitorde kalkıp da 47fps lik bir rakamı oluşturmak biraz zor. Yani ekran update'imiz monitor ile sync değilse eğer, monitor ışın tabancası ekranın ortalarında bir yerde ise, biz ekranın üst kısmını update işlemine giriyor olabiliriz. Sonra update hızı monitor ışın tabancasını yakaladığı anda da monitor tabancasının alt kısmındaki yerleri update ediyor oluruz (tabi tabanca durmuyor o da devam ediyor yürümeye) o zaman monitor üst kısmı birinci frame'den tabancanın alt kısmını da ikinci frame'den göstermeye başlar.. Ve ortada böyle bi çizgi gibi bir şey görmeye başlarız. Bu da aynı önceki gibi.. "u-uh.. No ... Olmaz" gibi bir durum. Yani öyle bi ayarlamalıyız ki ya monitorun refresh hızında bir fps seçelim ya da onun bi katı (yani integer katı.. Ama inverse.. 1/2 1/3 1/4 gibi) diyelim açacağımız ekran 75 hz'lik o zaman 75 fps seçebiliriz. Bu da bize frame başına 13.33333ms gibi bişi bırakır. Baktık bu süre yeterli değil bizim için(ki aslında 75fps de fazla hani...) O zaman 37.5fps gibi bi fps seçebiliriz. 26.6666 ms ayırıyoruz şu an frame başına ve her iki taramada bir frame update geçiyoruz. 37.5 fps oluyor. Ama.. Yok kardeşim. Ben böyle ondalık istemiyorum diyorsak o zaman.. 60hz'lik bi ekranda 2 frame'de bir update ile 30fps'lik bir görüntü veya.. 80hz'lik bi ekranda 2 frame'de bir update ile 40 fps'lik bir görüntü (ki bu durumda 25ms kalıyor frame başına). Örnek olarak 40fps'yi hedef seçelim diyelim ki. Bu durumda bizim hedefimiz ekranımızı her 25ms'de bir update etmek olacak. Ve monitor refresh problemini halletmek için de 80hz'lik bir mode açıp her iki taramada bir update çekeceğiz. (bu demek değil ki monitor taramayacak... tabi ki o framebuffer'da ne varsa onu tarayacak. Biz sadece her 25ms'de bir o framebuffer'i update edeceğiz o kadar.)

Şimdi izleyeceğimiz yöntem şu şekilde: Bizim bir timeline'imiz var di mi ? Yani sıfırıncı ms'de başlayıp öyle giden...

0-------------25------------50-----------75---------100----------125----------- 1.frame 2.frame 3.frame 4.frame 5.frame ....

Hah aynen böyle.. Programımız başlar (demo!) Ve biz normal bir şekilde yapıyor olsaydık, timer'dan her 25 ms'de bir 'update' event'i yaratıp ekranımızı update ediyor olurduk. Yani.. 0.cı ms'deyiz. Hesaplayacaklarımızı hesaplıyoruz, o sırada tabii ki time akıyor durmuyor time.. Oluyor diyelim 15ms.. Sonra cpu'ya diyoruz ki "alo hadi yaptık biz yapacağımızı sen iyisi mi çiz!". Cpu hali ile gpu'ya signal yolluyor "bak insanlar bekliyor çizim gerekiyor şu an hadi göster kendini" diye. Gpu'da haşırt çizmeye başlıyor. Ancak! Biz eğer monitör taraması ile kendimizi sync etmek istiyorsak, şöyle durumlar olabiliyor.. (1.frame'i zoomluyorum.)

1. Frame total usage Time 0------------5-------------10---------------15------------20-----------25

_____________________________________________ Cpu | cpuwork |_______cpuidle____________

__________________ Gpu _____________gpuidle_________________________| gpuwork |_waitvbl

Burada gösterdiğim standart bir cpu ve gpu graph'i. Biraz kötü dizayn edilmiş bir oyunun veya demonun çalışma şekli. Cpu ve gpu'ya farklı zamanlarda erişilerek, birbirlerinin paralel çalışması engellenmiş ve olabilecek minimum performans alınmış. İşte bütün bunları engellemek için insanlar çeşitli yöntemler bulmuş. Çünkü hedef grafik:

1. Frame total usage Time 0------------5-------------10---------------15------------20-----------25

________________________________________________________________________ Cpu |

_____________________________________________________________ Gpu ___________|

Buna yakın bir şey olmalı. Bu tabii imkansız. Yani kimse bu kadar iyi sync yapamaz. Ama elimizden geleni ardına koymamalıyız ve ona göre düşünmeliyiz.

Şimdi bunun için nasıl bir timing tekniği kullanacağımıza bakalım öncelikle... Bu teknik için 2 adet timer varmış gibi düşünüyoruz. Birincisi gerçek timer, ki bu sistemden gidip gerçek time value'sunu alan birim. İkincisine sanal timer diyebiliriz. Bu da aslında sistemin kullanacağı timer. Şimdi bütün programımızı bu noktada azıcık task oriented düşünüyoruz. Sistem standard'ın birazcık dışına çıkıyor. Buna daha sonradan şu task/thread manager kısmında bayağı bir değineceğim.

Şimdi.. Programımız aslında bir nevi iki timer'ı da kullanıyor. Ana olan bütün olaylar gerçek timer'ı kullanıyor.. Ancak hesaplama çizme gibi kısımlar virtual timeri. Şöyle oluyor. Hep beraber time'la birlikte ilerleyerek bakalım. 0.ms -> başlıyoruzzzzzzzzzzzzzzzzzzzzz... 4.ms -> diyelim aha bu noktada ekrana bir cube gelmesi gerekiyor. Hemen o kübü getiren task'i (dikkat!!!!!! - execute etmiyoruz).. Gereken task'i bir queue'ya atıyoruz. Daha çalışmadı kodumuz 13.ms -> aha burada da diyelim o sırada bir texture hesaplanması gerekiyor diyelim (tamamen uyduruyorum bunları) o texture'u da hesaplamıyoruz. Ve direkt queue'ya atılıyor.

Tamam diyelim ilk frame'de bunlar oluyor. Gerçek zaman.. Gerçekten ilerledi ve bu tip işlemlerin olacağını gördü. Ve bunlar bir şekil queue'da toplandı. Sonra real-timer bakıyor ki.. 25.ms oldu.. Hah işte bu noktada artık frame update olması gerekiyor. Çünkü bu frame için yapılması gereken iş listesi belirlendi ve artık frame update gerekiyor. Ama geldik frame'in sonuna henüz bir şey yapılmış değil evet... Bu işlemlerin yapılışına aslında bu frame de başlayacağız... Pekiiiii... Ne zaman göstereceğiz... Cevap: bir sonraki frame.. Yani tamamen bir frame delay yiyoruz. (halbuki bu frame'de göstermemiz gerekirdi) ama sağladığı avantaj: o frame'i çizmeye başladığımız andan itibaren bir frame oluşturulması gereken işleri aslında bir nevi sort ettik. Ve 25.ci ms'de şu an cpu biliyor nelerin yapılmasını 1.frame'i çizerken.. Arka arkaya onları execute ediyor. (hiç bir stop yok cpu'da.. Hali ile gpu'da da (zati farklı thread'lerde çalışıyor ekran update gibi olaylar.. Buna da değineceğim şu an kurcalamasın ortalığı)..) Cpu direkt olarak full güç execute ediyor ve hiç bir wait yemiyor. Ve aynı anda gpu'da full güç tüm drawing olaylarına başlıyor ve hiç bir wait yemiyor taaaaaaaaaa ki.. İşleri bitene kadar.. Yani gerçek ikinci frame'in (yani bizim aslında birinci frame) başlangıç ve bitiş grafiğine bakacak olursak eğer:

2. Frame total usage Time 25------------30------------35--------------40------------45-----------50

______________________________ Cpu | |__________________________________________

__________________________________________________ Gpu ___| |__________________

Şekline uygun bir yapı çıkar. Cpu başladı çalışmaya. Biraz yaptı işini (öncelikle gpu için gerekenleri falan..) Sonradan gpu'da başladı çizmeye yapılanları... Ve aynı anda biri hazırlananları çizerken diğeri de gereken diğer işlemleri yapmaya devam ediyor. Ve %90 ihtimalle cpu işini daha önce bitiriyor. Ve gpu azıcık daha çizmeye devam ediyor. Eee. Hani boşluk kalmayacaktı... Hah işte kalmayacak.. Demek ki kötü bir şey yapmışız. Yani bizim program şu an haaaaaaaala resource kaldırabilecek durumda.. (tabi bu arada.. Bu 2.frame'deyken.. Bizim 2.ci frame için gereken task'lar da bir yandan demo flow tarafında queue'ya push'lanmaya devam.. diyelim bu frame'de hodo hodo noise hesaplanması gerekiyor. Execution yok.. Sadece basit bir push işlemi olduğu için sistemi zati kullanmıyor. O hodo hodo noise aslında 3.cü frame'de execute edilecek. Yukarıdaki örneğimizde aslında belki de o hodo hodo noise'u bu frame'e taşıyabilirdik.. Gördüğümüz gibi haaaaaala imkanımız var o işlemleri yapacak gpu ve cpu belli bir kısımda boş.. Ama artık o boşluğun nerede olacağı ne zaman olacağı tamamen kontrolümüz altında. Yükü ağırlaştırıp hafifletmek elimizde. Örneğin belki bir rotation işlemi (varsayalım 40000,40000,40000'lik bir volume texture'u böyle sinus hodo cosinus bir şey yapıyoruz... Ama dönmesi biraz yavaş olması bizim için çok da problem değil. O zaman bunu biz 2 frame'de bir döndürelim. Ve gene aynı şekilde buna benzer gene çok zaman yiyebilecek bir başka process'de var diyelim o da gene aynı şekilde 2 frame'de bir çalışsa problem olmayacak. O zaman bunları hesaplayacak tasklarımızı bu tip frame'lere bölersek kesinlikle çok daha performanslı sonuçlar alabiliriz.. Bunlara sırası ile task a ...ve task b diyelim.

Time 0------------25------------50--------------75------------100----------125 1.frame 2.frame 3.frame 4.frame 5.frame push task-a process task-a process task-b process task-a process task-a push task-b push task-a push task-b

Gibi bir görüntü çıkacak. Yani belki bazı frame'lerde tam istediğimiz gibi gözükmeyecek yani hem task a'nin o saniyede ki hali ve hem de task b'nin o saniyede ki hali... ama.. bir nevi process'lere bölüm scheduling sistemi bunu yükten kurtaracak. Öbür türlü yaptığımızda belki de 40fps'lik olayımız aslında 20fps olacaktı.. Yani olayı bir nevi workcompression gibi düşünün. (bilenler için.. Pal görüntü gibi.. Odd fields - even fields... Her frame'de sadece ya odd field'lar gönderilir.. Ya da even... Ancak ve ancak ikisi bir olunca bir frame olur.. Ama birinci frame'in odd'u geldiğinde.. Aslında ikinci frame'in even'i olabilir elimizde. Ek bir bilgi olarak yuv encoding de benzeri bir prensiple çalışır.)

Bu sistemin getirdiği başka bir avantaj ise, birden çok cpu'muzun olduğu durumlar için geçerli. Yani elimizde her zaman execute edilmesi beklenen bir group task var. Ve bunların execution'ını biz aslında birbirinden bağımsız olarak değişik processorlere yaptırabiliriz. Hali ile

2. Frame total usage Time 25------------30------------35--------------40------------45-----------50

___________ Cpu1 | |___________________________________________________________ ___________ Cpu2 | |___________________________________________________________

__________________________________________________ Gpu ___| |__________________

Haline dönüştürebiliriz. Bu da aslında bizim için işlemciye çok daha fazla yüklenebileceğimiz manasına geliyor. Neticede her task bir iş yapıyor. Ve tasklar birbirinden tamamen bağımsız. Ve eğer hangi sıra ile yapıldıkları da önem taşımıyorsa o zaman bir task/thread manager gibi bir birim vasıtası ile eşit miktarda CPU'lara bölüştürülebilir. (Tam eşit olmaz tabii ki ama.. Olduğu kadar)... Bunun nasıl olacağına o birimi incelerken değineceğim. Ama çok basit mantığını anlatmam gerekirse iki thread olduğunu düşünün ve bunların wait state'de olduğunu düşünün. Ve ortada bir queue var. Eğer queue'ya yeni bir task/mesaj bir şey gelirse bunlardan biri uyanıyor ve queue'dan onu alıp execute'a başlıyor.. Sonra bir adet daha gelirse ikincisi de uyanıyor. V.s. Eğer birisi işini bitirirse tekrar wait state'e gelmeden queue'ya tekrar bakıyor ve eğer queue'da daha birkaç adet task daha varsa gene onlardan birini alıp execute mode'una giriyor. Neyse fazla kafa karıştırmadan bunu daha sonra anlatayım.

Neyse umarım bu timer konusu ve sync olayları hakkında bir bilgi verip temel bir design'ın nasıl olması gerektiği konusunda aydınlatabilmişimdir. Bu yazıya devam edeceğim kaldığım yerden şimdi diğer konulara dallanırsam çok uzayabileceği inancındayım. Bir sonraki yazımda task/thread manager kısmına değinmeyi düşünüyorum. Daha ileriki bölümlerde ise memory manager / resource manager ve temel şeyleri yapan bir gfx lib'i nasıl yapmalıyız şeklinde devam edeceğim ilk design'ımıza. 'Lord of the demos' adlı yazı dizisi devam edecektir.

ciao!

~sensei/rt - csermen@gmail.com

******************************************************************************** ÖĞRETİCİ DÖKÜMAN : SDL_MIXER İLE SES VE MÜZİK

SDL_Mixer SDL kütüphanesinde ses ve müzik kullanımını kolaylaştırmak için geliştirilmiş bir kütüphanedir. Ses ve müzik dosyalarını yüklemek ve çalmak üzere işinizi kolaylaştıracak birçok fonksiyona sahiptir. Öncelikle SDL_Mixer kütüphanesi ile kullanabileceğiniz dosya formatları; - WAVE/RIFF (.wav)

- AIFF (.aiff) - VOC (.voc) - MOD (.mod .xm .s3m .669 .it .med ve fazlası) - MIDI (.mid) - OggVorbis (.ogg) - MP3 (.mp3)

Hemen kullanımını anlatmaya geçiyorum. Projenize SDL_Mixer'ı eklemek için derleyiciye -lSDLMixer bayrağını eklemeniz gerekmektedir. Include etmeniz gereken tek bir dosya var, SDL_Mixer.h dosyası. Bunun dışında SDL_Mixer'ı kullanırken dikkat etmeniz gereken bir şey daha var. SDL_Mixer'ı kullandığınız sırada SDL'in kendi içindeki ses sistemi ile ilgili birkaç komutu kullanamıyoruz. Bunlar; SDL_OpenAudio Yerine Mix_OpenAudio'yu kullanın. SDL_CloseAudio Yerine Mix_CloseAudio'yu kullanın. SDL_PauseAudio Yerine Mix_Pause(-1) ve Mix_PauseMusic'i kullanın. Yerine Mix_Resume(-1) ve Mix_ResumeMusic'i kullanın. SDL_LockAudio Bu işi SDL_Mixer sizin için hallettiği için kullanmanız gerekmeyebilir. Yine de kullanmak problem yaratabilir. SDL_UnlockAudio Bu işi SDL_Mixer sizin için hallettiği için kullanmanız gerekmeyebilir. Yine de kullanmak problem yaratabilir.

Aşağıdaki komutları ise rahatlıkla kullanabilirsiniz.

SDL_AudioDriverName Normal şekilde çalışacaktır. SDL_GetAudioStatus Bu da sorunsuzca çalışacaktır, ama büyük ihtimalle sessiz durumdayken bile SDL_AUDIO_PLAYING değerini döndürecektir.

SDL_Mixer içindeki fonksiyonlara ve kullanımlara gelirsek, önce Mix_OpenAudio ile başlamalıyız. Bu komut ile SDL_Mixer ses sistemini açmaya yarar.

int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)

frequency ses çıktısının frekans değerini belirtir. Oyunlar için uygun bir değer olarak MIX_DEFAULT_FREQUENCY adıyla tanımlanmış olan 22050 değerini kullanabilirsiniz. Ayrıca 44100 değerini de kullanabilirsiniz ki bu değer CD audio frekans değeridir. Ama bu değer daha fazla CPU gücü tüketir.

format ses çıktısının formatını belirtir. Özel bir durum yoksa MIX_DEFAULT_FORMAT değeri işinizi görür.

channels ses çıktısının kaç kanallı olduğunu belirtir. Stereo için 2, mono için 1 değerini verin.

chunksize ise ses çıktısı için kullanılacak byte'lardır.

Format için kullanabileceğiniz diğer seçenekler: AUDIO_U8 İşaretsiz 8-bit sesler AUDIO_S8 İşaretli 8-bit sesler AUDIO_U16LSB İşaretsiz 16-bit samples, little-endian byte düzeninde AUDIO_S16LSB İşaretli 16-bit samples, little-endian byte düzeninde AUDIO_U16MSB İşaretsiz 16-bit samples, big-endian byte düzeninde AUDIO_S16MSB İşaretli 16-bit samples, big-endian byte düzeninde AUDIO_U16 AUDIO_U16LSB ile aynı (geriye uyumluluk için büyük ihtimalle) AUDIO_S16 AUDIO_S16LSB ile aynı (geriye uyumluluk için büyük ihtimalle) AUDIO_U16SYS İşaretsiz 16-bit samples, sistemin byte düzeninde AUDIO_S16SYS İşaretli 16-bit samples, sistemin byte düzeninde

MIX_DEFAULT_FORMAT AUDIO_S16SYS ile aynıdır.

Chunksize için uygun bir değer olarak 4096 verebilirsiniz. Ama ses efektlerinin arkaplanda gereğinden uzun süre sürdüğünü düşünüyorsanız bu değeri küçük tutmayı deneyebilirsiniz. Yalnız değer küçüldükçe yavaş sistemlerde ses çıktısının atlanması olasıdır.

Fonksiyon başarılı olarak sona ererse 0, hata verirse -1 değerini döndürür.

ses ortamını açmak için örnek kod: if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)==-1) { fprintf(stderr, "Mix_OpenAudio: %s\n", Mix_GetError()); exit(2); } veya:

if(Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096)==-1) { fprintf(stderr, "Mix_OpenAudio: %s\n", Mix_GetError()); exit(2); }

Ses sistemini açtıktan sonra kapatmak için ise Mix_CloseAudio fonksiyonunu kullanırız. Kullanımı oldukça basittir.

kapatmak için örnek kod: Mix_CloseAudio();

Ses sistemini açıp kapatabiliriz. Şimdi sırada biraz ses çıkarmak var :). SDL_Mixer ile birçok yaygın formattaki ses dosyasını kolayca hafızaya yükleyip çalabilirsiniz. SDL_Mixer'da çalacağınız dosyalar iki tiptir. 1- Sample 2- Music

Sample'lar basit ses efektleridir diyebiliriz. Veya ufak ses dosyaları. Daha da basitçe müzik parçası olmayan ses dosyalarıdır dersek sanırım yanlış olmaz. Hafızada bir sample verisi saklarken kullanacağımız yapının adı Mix_Chunk'tır. Ses dosyasını hafızaya yüklerken kullanacağımız komutun adı da Mix_LoadWAV()'dır. Adı sizi yanıltmasın, bu fonksiyon sadece wav dosyalarını değil; wave, aiff, riff, ogg ve voc dosya formatlarını okuyabilir.

wav yüklemek için örnek kod:

Mix_Chunk *sample;

sample=Mix_LoadWAV("sample.wav"); if(!sample) { fprintf(stderr, "Mix_LoadWAV: %s\n", Mix_GetError()); // hata olursa diye }

Programımızı kapatırken de yüklediğimiz ses dosyalarını hafızadan silmek için Mix_FreeChunk komutu kullanılır.

örnek: Mix_FreeChunk(sample); sample=NULL;

Ses dosyalarını çalmak da bu kadar basit, Mix_PlayChannel komutu ile gerçekleştiriliyor.

çalmak için:

if(Mix_PlayChannel(-1, sample, 1)==-1) { fprintf(stderr, "Mix_PlayChannel: %s\n", Mix_GetError()); }

İlk parametre ses dosyasını çalacak kanalı belirtir. -1 değeri ilk uygun ses kanalına bu görevi verir. İkinci parametre çalınacak olan ses dosyasıdır. Üçüncü parametre sample'ın kaç defa çalacağını belirtir. Bir hata oluşursa sample çalınamazsa fonksiyon -1 değerini döndürür.

SDL_Mixer içinde ayrıca ses dosyalarını çalarken basit efektler de uygulamanızı sağlayan fonksiyonlar bulunmaktadır. Örneğin Mix_FadeInChannel fonksiyonu ile bir ses dosyasını (sample) fade in (yavaş yavaş ortaya çıkma) efekti uygulayarak çalabiliriz.

if(Mix_FadeInChannel(-1, sample, 1, 1000)==-1) { fprintf(stderr,"Mix_FadeInChannel: %s\n",Mix_GetError()); }

Bütün parametreler Mix_PlayChannel fonksiyonu ile aynıdır bu fonksiyonda. Sadece son parametre olan 1000 değeri efektin kaç milisaniye süreceği değerini bildirir. Bir de bu fonksiyonun tersi olarak Mix_FadeOutChannel fonksiyonu bulunmaktadır.

Mix_FadeOutChannel(int channel, int ms);

Bu fonksiyon ile belirtilen kanal belirtilen milisaniye içinde sesi kısılarak kapatılır. Bu komut zaten çalmakta olan bir kanala uygulanacağı için ses dosyasını belirtmeye gerek yoktur. Ayrıca Mix_HaltChannel komutuyla sesi anında kapatabilirsiniz.

Mix_HaltChannel(int channel);

şeklinde uygulanır. Kanal değeri olarak ya kapatmak istediğiniz kanalın numarasını ya da bütün kanalları kapatmak için -1 değerini verebilirsiniz. -1 değeri diğer bütün fonksiyonlarda da aynı şekilde bütün kanalları belirtir. Kanallarla ilgili yapabileceklerimiz arasında kanalın ses seviyesini değiştirebilirsiniz.

Mix_Volume(int channel, int volume);

Channel ile kanalın numarası ya da -1 ile bütün kanallar belirtilebilir. Volume ise sesin yüksekliğinin belirtildiği değişkendir. Bu fonksiyonun bir de ses dosyasına özel ses seviyesi belirtebileceğiniz versiyonu bulunmaktadır.

Mix_VolumeChunk(Mix_Chunk *chunk, int volume);

Chunk değişkeni ile verilen ses dosyasının ses yüksekliği volume değişkenine göre değiştirilir.

Sırada müzik dosyaları ile ilgili işlemler var.

Mix_Music *music;

Mix_Music veri yapısı hafızada müzik dosyalarını saklayacağımız yapıdır. Müzik dosyaları Mix_LoadMUS fonksiyonu ile yüklenir.

if(!(music=Mix_LoadMUS("music.mp3"))) { fprintf(stderr,"Mix_LoadMUS(\"music.mp3\"): %s\n", Mix_GetError()); }

Bu fonksiyon ile wav, mod, midi, ogg ve mp3 dosyalarını yükleyebilirsiniz.

Mix_FreeMusic(music); music=NULL;

Yukarıdaki yöntem ile de işimiz bittiğinde ya da programı kapatırken müzik dosyalarının verilerini hafızadan temizleriz.

Müzik dosyalarını çalmak için Mix_PlayMusic fonksiyonu kullanılır.

if(Mix_PlayMusic(music, -1)==-1) { fprintf(stderr, "Mix_PlayMusic: %s\n", Mix_GetError()); } music parametresi çalınacak müzik dosyasını belirtir. -1 değeri ise dosyanın sonsuz döngüde çalınacağını belirtir. -1 yerine parçanın tekrar edilmesini istediğimiz sayıyı yazabiliriz.

Fade in efektini sample'lar gibi müzik dosyalarına da uygulayabiliriz.

if(Mix_FadeInMusic(music, -1, 2000)==-1) { fprintf(stderr, "Mix_FadeInMusic: %s\n", Mix_GetError()); }

Fade in efekti olduğu gibi Fade out efekti de vardır. O ise sadece tek parametre alır. Fade out efektinin süresi (milisaniye cinsinden).

Mix_FadeOutMusic(3000);

Mix_PlayMusic fonksiyonu ile aynıdır her şey. Sadece son değer Fade in efektinin tamamlanacağı süreyi milisaniye cinsinden belirtir.

Bir müzik dosyasının çalmasını durdurmak için iki fonksiyonumuz vardır. Mix_HaltMusic ve Mix_PauseMusic. Mix_HaltMusic müzik çalınmasını hemen durdurur. Bütün fade efektlerini de keser. Mix_PauseMusic fonksiyonu ise çalmakta olan müziği sadece duraklatır. Mix_ResumeMusic fonksiyonu ise duraklatılan yerden çalmaya devam edilmesini sağlar. Bu fonksiyonu durdurulmuş (Halt), duraklatılmış (Pause) veya çalmakta olan müzik ile kullanmak güvenlidir, hataya neden olmaz.

Mix_HaltMusic(); Mix_PauseMusic(); Mix_ResumeMusic();

Evet bu doküman ile SDL_Mixer kullanımına giriş yaptık. SDL_Mixer'ın bütün yeteneklerini anlatmadım size ama anlattıklarım ile SDL_Mixer kullanmaya hemen başlayabilirsiniz. Gerisi için şimdilik orijinal SDL_Mixer dokümantasyonuna (http://jcatki.no-ip.org/SDL_mixer/ SDL_mixer_frame.html) bakmalısınız. Daha sonra SDL_Mixer'ın daha detaylı kullanımını anlatan bir doküman daha yazabilirim.

Not: 1 saniye == 1000 milisaniye; ;) Ragnor/Manyetiq