Archive for Computer Science & IT

O profilowaniu, cyklach i kodzie z odzysku

2007-07-12 15:54

Dla odmiany dzisiaj napisałem niewielką, ale bardzo wiele mówiącą o każdym kodzie rzecz – czyli profiler. Już wyjaśniam, że jest to moduł służący do kontrolowania wydajności: dzięki odpowiednio umieszczonym wywołaniom można przy jego pomocy określić, ile czasu zajmuje wykonywanie poszczególnych części programu.

Zasadniczą częścią jest tu oczywiście jakiś sposób pomiaru czasu. Dawniej, pisząc moduł profilujący, przygotowałem go tak, by oprócz czasu faktycznego w (mili/nano)sekundach podawał też ilość cykli procesora przypadających na daną operację. Na procesorach x86 można to zrobić przy pomocy rozkazu RDTSC. Obecnie porzuciłem tę koncepcję z kilku powodów, przy czym najbardziej prozaiczny jest ten, że… nigdy mi się taka funkcjonalność nie przydała :) Poza tym obecnie wartość zwracana przez wspomnianą instrukcję (a jest to liczba cykli procesora od momentu ‘resetu’) jest mało wiarygodna, jako że zdarzenia w rodzaju wstrzymywania czy hibernacji systemu bardzo lubią ją resetować. I wreszcie: ciężko jest przecież optymalizować kod pod kątem pojedynczych cykli procesora, jeśli pisze się głównie w języku wysokiego poziomu.
Wspomniałem, że to nie jest mój pierwszy profiler. W istocie, tak naprawdę dokonałem teraz zwyczajnego przepisania go (czy raczej drobnego przerobienia jego kodu) tak, by pasował do nowej wersji mojej biblioteki kodów wszelakich. (Bo cały czas bronię się, żeby nazywać ją silnikiem :)). W sumie więc nie jest to jakieś wielkie osiągnięcie, ale przynajmniej dowodzi tego, że kod napisany parę lat wcześniej nie musi od razu iść do kosza. Aczkolwiek jeśli chodzi o tę poprzednią wersję biblioteki, to raczej nic ciekawego już w niej nie znajdę :]

Wypadałoby teraz powiedzieć co nieco o tym, w jaki sposób tego starego-nowego profilera się używa. Otóż jest to całkiem proste. Bazując na jednym z rozdziałów pierwszego tomu Perełek programowania gier zorganizowałem pomiar czasu hierarchicznie. Można zatem korzystać z tego, że mniejsze operacje są częścią większych i tak je profilować – na przykład:

  1. Profiler.Begin ("Frame");
  2. Profiler.Begin ("Update");
  3. /* (tutaj kod uaktualniający elementy sceny) */
  4. Profiler.End();
  5.  
  6. Profiler.Begin ("Render");
  7. /* (a tutaj renderowanie) */
  8. Profiler.End();
  9. Profiler.End();

Dzięki temu można sprawdzać nie tylko maksymalny, minimalny i średni czas generowania całej ramki gry, ale też te same czasy osobno np. dla samego renderowania. Dalej można by jeszcze dodać statystyki dotyczącego tego, ile czasu (minimalnie, maksymalnie i średnio) zajmują procentowo czynności “mniejsze” względem “większych”.

Ale to już wymaga dopisania od podstaw, więc w przeciwieństwie do recyklingu starego kodu nie byłoby takie proste :)

Tags:
Author: Xion, posted under Programming » 5 comments

Partikle!

2007-07-11 20:55

Żeby zwiększyć szansę na to, by moje devlogowanie nie było tylko pobożnym życzeniem, od razu spróbuję przejść do rzeczy. Zaprezentuję mianowicie to, co udało mi się napisać w ostatni weekend.

Jako sposób na przypomnienie sobie DirectX’a, postanowiłem zrealizować prostą, ale efektowną “sztuczkę” znaną jako system cząsteczkowy. Dla mniej zorientowanych mogę wyjaśnić, że jest to sposób na generowanie ciekawych efektów graficznych stosunkowo małym wysiłkiem przy pomocy dużej ilości tzw. cząstek – albo też “partikli”, bo całość to przecież particle system. Takie cząstki mają zwykle dość prozaiczne właściwości, jak pozycja i prędkość (odpowiednio aktualizowane), a w praktyce są renderowane jako kwadraty zwrócone do kamery i pokryte określoną teksturą.

Swój system oparłem w dużym stopniu na zawartości 18. i (zwłaszcza) 19. rozdziału książki Programowanie gier w DirectX – doskonałej zresztą. Jak przystało na porządny system jest więc opisywanie właściwości cząstek za pomocą zewnętrznych skryptów. Za najbardziej pomysłowy uważam jednak opisany w rzeczonej książce mechanizm zdarzeń, czyli sposób na opisanie życia każdej cząstki – od poczęcia do naturalnej śmierci ;)
Weźmy na przykład taki dym. Po utworzeniu cząsteczka dymu na kolor jasnoszary, ale z czasem robi się ciemniejsza i coraz bardziej przezroczysta. Ponadto dym rzadko unosi się pionowo do góry, zatem początkowy wektor prędkości powinien co jakiś czas mieć losowo zmienianą składową poziomą.

Wszystko to można opisać w skrypcie, ustanawiając zdarzenia – czyli mówiąc, co spotka cząstkę np. w 3 sekundzie życia. Może to być na przykład coś takiego:

  1. event
  2. {
  3. at = 3
  4. velocity_x = { set, "random(-1,1)" }
  5. }

co – jak nietrudno się domyślić – jest ustawieniem składowej X prędkości na wartość z zakresu [-1;1]. Ciekawsza jest interpolacja liniowa parametrów:

  1. event
  2. {
  3. at = end
  4. color_a = { lerp, 0 }
  5. color_r = { lerp, 0 }
  6. color_g = { lerp, 0 }
  7. color_b = { lerp, 0 }
  8. }

W ten sposób cząsteczka będzie się stawać coraz ciemniejsza i coraz bardziej przezroczysta, bo i alfie, i trzem składowym koloru każemy liniowo zmniejszać się do zera. Całe zdarzenie nie ma przypisanego stałego czasu, a jedynie oznaczenie, iż ma się wykonać w chwili śmierci cząsteczki. W tym przypadku aczkolwiek znaczy to tylko tyle, że w tym momencie cząsteczka będzie już całkiem czarna i zupełnie niewidoczna, lecz interpolacja jej koloru będzie przeprowadzana przez cały czas (od ew. poprzedniego zdarzenia).
Zaprogramowanie tego mechanizmu było swoją drogą nieco kłopotliwe. W książkowym oryginale zdarzenia mogły zmieniać tylko jeden parametr cząstki, więc implementacja płynnego przejścia nie była trudna. W mojej wersji można naraz zmieniać dowolną ilość właściwości oraz mieszać ze sobą natychmiastowe (set) i płynne (lerp) zmiany. Ostatecznie skończyło się na trochę pokręconym sprawdzaniu różnicy między czasem, w którym zdarzenie ma wystąpić, a aktualnym zegarem cząsteczki.

Ano właśnie – nie czasem życia, tylko zegarem. Bo cząstkę można też zapętlić, dzięki czemu będzie wiele razy przechodziła przez te same zdarzenia. Wystarczy “oszukać” jej zegar:

  1. event
  2. {
  3. at = 0.5
  4. velocity_x = { set, "random(-1,1)" }
  5. }
  1. event
  2. {
  3. at = 1
  4. timer = { set, 0.01 }
  5. }

i już mamy taki oto śnieg:

Śnieg jako efekt cząsteczkowy

Przy okazji muszę stwierdzić, że mechanizm publikowania obrazków na Bloggerze to czysta poezja. Nie dość, że można uploadować własne pliki, to jeszcze miniaturki tworzone są automatycznie. Tak tak – nie krępuj się i kliknij :)

Może nie wygląda on zbyt realistycznie, ale jego tekstura była robiona bardzo na szybko ;P Zresztą, w efektach cząsteczkowych partikle (coraz bardziej lubię to słowo :D) są zwykle bardzo małe, a końcowe wrażenie jest oparte na wyglądzie całej tej “masy”, a nie pojedynczych cząstek. Tak czy owak widać jednak, że systemy cząsteczkowe to całkiem przydatny mechanizm tworzenia ładnego dymu, śniegu, kurzu, efektów magicznych, że nie wspomnę o błyskach towarzyszących zbieraniu różnych power-upów :) Mała acz zgrabna rzecz.
Przy czym ‘mała’ to pojęcie dość względne, jako że ostatecznie system zamknął się w ok. 1500 linijkach kodu – i to nie licząc parsera skryptów (który oparłem na opracowanym jakiś czas temu formacie XVL) i używanego też parsera wyrażeń matematycznych. Całkiem niezły wynik jak na dwa dni kodowania :)

Tags: , ,
Author: Xion, posted under Programming » 1 comment
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.