Posts from 2009

Shadery i pliki efektów – sprostowanie

2009-12-11 16:58

Płynne przejście od programowania grafiki 3D przy pomocy fixed pipeline do wykorzystania shaderów nie jest z początku takie proste. Jest oczywiście w sieci mnóstwo tutoriali, które to ułatwiają. Zauważyłem jednak, że mają one tendencję do przekazywania naciąganych – mówiąc delikatnie – faktów na temat tego, w jaki sposób shadery oraz pliki efektów .fx (często omawiane łącznie) muszą być wykorzystywane w aplikacjach DirectX.
Dlatego pomyślałem sobie, że dobrze byłoby sprostować kilka mitów, jakie się tutaj pojawiają i wprowadzają zamieszanie do i tak niełatwej dziedziny programowania. Warto bowiem wiedzieć, iż to nieprawda, że:

  • …korzystając z shaderów, musimy używać też plików .fx. Shader wierzchołków czy pikseli możemy samodzielnie wczytać z pliku (w wersji skomplikowanej lub przeprowadzić kompilację przy pomocy funkcji D3DX) czy nawet wygenerować dynamicznie, a potem zwyczajnie ustawić go jako aktualny VS/PS przy pomocy metod SetVertex/PixelShader urządzenia DirectX. Plik efektów nie jest wtedy do niczego potrzebny.
  • …pliki .fx są tylko po to, by łatwiej wczytywać/aplikować shadery do renderowania. W rzeczywistości celem istnienia plików efektów jest uproszczenie kodowania różnych efektów graficznych w wersjach zależnych od możliwości poszczególnych kart. Zamiast samodzielnie sprawdzać capsy (możliwości) sprzętu, na którym uruchamia się nasza aplikacja, możemy po prostu pozwolić DirectX-owi wyszukać w pliku .fx tą wersję efektu (tzw. technikę), która na danej konfiguracji sprzętowej będzie działać. W ten sposób możemy na przykład napisać dwie wersje oświetlenia: jedną per pixel z użyciem shaderów i drugą, używającą fixed pipeline i oświetlającą wierzchołkowo. Nie musi ona wtedy korzystać z żadnych shaderów.
  • …aby stosować shadery, musimy porzucić FVF na rzecz vertex declaration. Jest to piramidalna bzdura, która na szczęście rzadko bywa wygłaszana wprost, ale często wynika ze sposobu omawiania tematu VD w tutorialach. W istocie stałe D3DFVF tak samo dobrze opisują format danych dla vertex shaderów, jak deklaracje wierzchołków (IDirect3DVertexDeclarationX) i DirectX nie ma problemu z łączeniem jednego z drugim. Analogia działa zresztą też w drugą stroną: użycie fixed pipeline nie wymusza korzystania z FVF.
    Oczywiście bardziej zaawansowane techniki pisane z użyciem shaderów najczęściej potrzebują na wejściu danych, których w FVF zapisać się nie da (np. wektorów stycznych i binormalnych). Wtedy rzecz jasna potrzebne jest określenie formatu wierzchołków poprzez vertex declaration. Dla prostych VS-ów (jak choćby zwykłego m4x4 oPos, v0, c4) sam fakt korzystania z programowalnego potoku grafiki nic nie wymusza w zakresie formatu danych wierzchołków.
  • …vertex i pixel shadery musimy zawsze stosować łącznie. To prawie logiczna konsekwencja częstego stwierdzenia (też niespecjalnie prawdziwego), że wyjście VS-a jest wejściem PS-a. W rzeczywistości nie jest to prawdą i łatwo można podać przykłady technik, w których możemy np. użyć pixel shadera bez vertex shadera – jak choćby zamiana wszystkich kolorów w scenie na odcienie szarości.
  • …shadery zastępują cały potok renderowania. Chociaż oświetlenie, teksturowanie, transformacje macierzowe wierzchołków itp. to spory kawałek potoku renderowania, zastąpienie ich shaderami wcale nie oznacza, że nic już poza tym nie zostało. Rzeczy takie jak alpha blending, usuwanie tylnich ścian (culling), różne testy pikseli (głębokości, alfa, stencil) i przycinanie (np. płaszczyznami oraz scissor test) to tylko niektóre z elementów potoku, które są dostępne niezależnie od tego, czy obecne są w nim także załadowane przez użytkownika shadery.
Tags: , ,
Author: Xion, posted under Programming » 2 comments

Triki z PowerShellem #12 – Rozpakowywanie

2009-12-07 23:18

Powtarzające się katalogiWiele programów z sieci wciąż jeszcze ściąga się w postaci archiwów do samodzielnego wypakowania, jak choćby w formacie .zip. Ma to swoje zalety i wady – do tych drugich należy fakt, że nie bardzo wiadomo, jak wygląda wewnętrzna struktura katalogów takiej paczki. Używając opcji typu Wypakuj tutaj ryzykujemy zaśmiecenie folderu Downloads plikami programu. Dlatego osobiście zawsze stosuję polecenie Wypakuj do nowego katalogu.
I tu czasem jest mały zonk, gdy twórca archiwum zdecydował się na spakowanie całego folderu, a nie tylko zawartych w nim plików. Powstają wtedy nadmiarowe katalogi, wydłużające ścieżki do plików (co widać obok – w wersji trochę przesadzonej ;)).

Ponieważ podobne sytuacje zdarzają mi się dość często, postanowiłem im zaradzić przy pomocy najlepszego narzędzia na takie okazje, czyli PowerShella rzecz jasna :) Wynikiem jest poniższy skrypt do sprytniejszego rozpakowywania archiwów:

  1. # unpack.ps1
  2. # Sprytne rozpakowywanie archiwów
  3. param ([string]$archive = $(throw "No archive specified"))
  4.  
  5. # Bierzemy nazwę archiwum i tworzymy odpowiadający mu katalog
  6. $name = [IO.Path]::GetFileNameWithoutExtension($archive)
  7. Set-Location -Path (New-Object IO.FileInfo @($name)).DirectoryName
  8. $dir = [IO.Directory]::CreateDirectory($name)
  9.  
  10. # Rozpakowujemy archiwum do tego katalogu
  11. $shell = New-Object -ComObject Shell.Application
  12. $src = $shell.Namespace($archive)
  13. $dest = $shell.Namespace($name)
  14. $dest.CopyHere($src.items())
  15.  
  16. # Rekurencyjnie badamy zawartość rozpakowanego archiwum
  17. while (($items = $dir.GetFileSystemInfos()) -eq 1)
  18. {
  19.     # Sprawdzamy, czy jego pierwszy i jedyny element jest katalogiem
  20.     $fsi = $items[0]
  21.     if (($fsi.Attributes -band [IO.FileAttributes]::Directory) -eq 0)
  22.         { break }
  23.    
  24.     # Jest - dokonujemy skrócenia ścieżki
  25.     $fsi.MoveTo([IO.Path]::GetRandomFileName())
  26.     $dir.Delete()
  27.     $dir = $fsi
  28.     $dir.MoveTo($name)
  29. }

Jego działanie polega wpierw na zwykłej dekompresji archiwum. Jak można zauważyć, używa do tego obiektu COM-owskiego Shell.Application. To sprawia, że skrypt ma pod tym względem te same możliwości co zwykły windowsowy Eksplorator (dla większych plików pokaże nawet pasek postępu ;]).
Później wypakowana zawartość jest poddawana operacji, którą nazywam tutaj ‘skróceniem ścieżki’. Polega ona wyrzuceniu jednego poziomu drzewa folderów, o ile tylko pewien katalog jest jedynym elementem swojego katalogu nadrzędnego. Takie właśnie sytuacje powstają przy dekompresji do nowego folderu archiwów źle zapakowanych (przynajmniej z mojego punktu widzenia ;P). Wynikiem działania skryptu będzie więc w sumie jeden nowy podkatalog zawierający bezpośrednio całą interesującą zawartość archiwum.

Oczywiście używanie powyższego skryptu tylko z poziomu linii komend PowerShella nie jest specjalnie wygodne; lepiej jest dodać go do menu kontekstowego archiwów, czyli np. plików .. O tym, jak można tego dokonać, napisałem dość obszernie przy okazji prezentacji skryptu do wysyłania przez FTP.

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

I co się w tej grze robi?…

2009-12-04 21:14

Przeglądając znalezione niedawno czasopisma o grach sprzed kilku lat, zauważyłem ciekawy schemat pojawiający się w ówczesnych recenzjach. Wiele z nich wskazywało mianowicie na liniowość jako cechę bardzo niepożądaną w każdej właściwie grze, niekoniecznie przygodówce czy RPG-u (gdzie istotnie byłaby ona zbrodnią). Gracz miał bowiem mieć jak najwięcej swobody i jak najwięcej możliwości dokonywania znaczących wyborów – wtedy rozgrywka uznawana była za interesującą.

Gdy przyjrzymy się teraz tytułom wychodzącym obecnie, to da się zauważyć, że lata forsowania tej idei zrobiły swoje. Już niewiele jest jakich gier, w których trzeba przechodzić kolejne etapy/misje/poziomy/itp. w dość ściśle określonej kolejności. Zamiast tego mamy coraz więcej produkcji, gdzie właściwie cały rozwój rozgrywki znajduje się w rękach gracza.
Przykłady? Chociażby cały podgatunek MMORPG: zwykle poza jedną linią głównych questów (zadań) wszystkie pozostałe są poboczne, a gra polega generalnie na współpracy z innymi graczami, by osiągnąć wyznaczone grupowo cele. Podobny schemat występuje też niekiedy w rozgrywce single player (np. cała seria Grand Theft Auto). W końcu mamy całą paletę gier symulacyjnych, z ikonicznymi “simsami” na czele.

Można więc przypuszczać, że liczba gier z dużym stopniem swobody dla gracza będzie się zwiększać. Po części jest tak może dlatego, że ciężko usłyszeć opinie, aby ten kierunek rozwoju był złym. Kto wie, może faktycznie tak nie jest? :)
Jednak mam pewne wątpliwości. Wysoka nieliniowość gry może bowiem oznaczać tyle, że jej twórcy poszli na łatwiznę i tak naprawdę nie zainteresowali się tym, jak będzie ostatecznie przebiegać rozgrywka. Postawienie na samą mechanikę i uczynienie jej maksymalnie elastyczną może z początku wyglądać na działanie wielce innowacyjne, ale po bliższym przyjrzeniu się na wierzch mogą wyjść duże zaniedbania od strony fabularnej. (Wydaje mi się na przykład, że to właśnie była przyczyna chłodnego przyjęcia długo oczekiwanej gry Spore).

Achievement unlocked :)Jest jeszcze jeden aspekt tej sprawy: czy wysoce nieliniowe gry są tym, czego gracze faktycznie oczekują? Nie byłbym tego taki pewien, a poważnym argumentem, który za tym przemawia, jest “ostatni krzyk mody” w grach wszelakich, czyli tzw. osiągnięcia (achievements). Twór ten przyszedł z produkcji konsolowych, a jego oczywistą funkcją jest przedłużanie czasu życia gry poprzez wskazywanie graczowi, co jeszcze mógłby w niej zrobić (i jak mógłby być lepszym od innych).
Popularność tego mechanizmu dowodzi, że jego wprowadzanie jest zazwyczaj dobrym krokiem. Dlaczego? Poza widocznym wyraźnie czynnikiem rywalizacji z innymi (zawsze pożądanym), osiągnięcia mogą organizować przebieg rozgrywki i subtelnie wskazywać jej właściwy kierunek.

Inaczej mówiąc, jest to sposób na ponowne wprowadzenie do gier liniowości – tym razem w wersji light, jako opcji. Czy oznacza to więc cofanie się w rozwoju? Otóż nie wydaje mi się; to raczej odpowiedź na zapotrzebowanie. Bo po co nam gry, w których można robić wszystko, skoro w rzeczywistości oznacza to, że niczego robić nie warto?…

Tags:
Author: Xion, posted under Games, Thoughts » 2 comments

Listy skoków w Windows 7

2009-11-30 22:44

Przykładowa lista skokówJednym z bardziej zauważalnych składników Windows 7, które odróżniają ten system od Visty, jest nowy wygląd paska zadań. Jest szerszy, wyświetla duże ikony i przesunięcie go z dołu na bok ekranu w końcu ma sens (hurra dla monitorów wide-screen). Ale nowy wygląd to w tym przypadku nie wszystko, bo pasek ten zyskał też trochę na funkcjonalnościach.
Wśród nich mamy tzw. listy skoków (Jump Lists), zastępujące tradycyjne menu sterowania. Listy te pojawiają się po kliknięciu prawym przyciskiem na ikonkę na pasku zadań.

Co zawierają takie listy? Jak widać z boku, ich elementami mogą być skróty do ostatnio otwartych w programie dokumentów. Żeby było zabawniej, będzie ona wygenerowana automatycznie nawet dla tych aplikacji, których twórcy w momencie ich pisania nie mieli bladego pojęcia o tym, że kiedyś będziemy mieli taki system jak Windows 7 :) W tej wersji bowiem sam Windows zarządza listami MRU (Most Recenty Used) dla poszczególnych programów, o ile tylko wywołują one funkcję ShAddToRecentDocs przy otwieraniu poszczególnych plików.
A to wbrew pozorom nie jest takie duże wymaganie, gdyż jest automatycznie ono spełnione, jeśli zachodzi jedna z poniższych sytuacji:

  • aplikacja ma zarejestrowane skojarzenie rozszerzenia plików i użytkownik otwiera przypisany jej plik przy pomocy Eksploratora Windows (np. dwukrotnie klikając)
  • program używa standardowych okienek dialogowych do otwierania plików (np. funkcji GetOpenFileName z WinAPI albo OpenFileDialog z .NET)

Na listę skoków możemy też dodawać własne pozycje w postaci tak zwanych zadań (tasks), działających jak zwykłe systemowe skróty i tworzonych w ten sam sposób (interfejs IShellLink). Szczegóły i przykładowy kod można znaleźć na przykład w tym artykule na CodeProject.

Tags: , ,
Author: Xion, posted under Applications, Programming » Comments Off on Listy skoków w Windows 7

Dwie uwagi o śpiących wątkach

2009-11-29 14:46

Wszyscy znamy doskonale funkcję Sleep, która w Windows API służy do zawieszania działania wątku na określony czas (podawany w milisekundach). Wydawałoby się, że musi to być najprostsza funkcja z tego API, jaką tylko można sobie wyobrazić – bo co może być skomplikowanego w “zwykłej pauzie”? A okazuje się, że jak najbardziej może :)

Używając Sleep – zwłaszcza w swej zwykłej wersji – musimy bowiem pamiętać przynajmniej o dwóch sprawach:

  1. Wątek, w którym wywołamy tę funkcję, zostanie zawieszony całkowicie aż do momentu, gdy podany interwał czasowy się skończy . W szczególności Sleep(INFINITE); sprawi, że właściwie możemy ów wątek wyrzucić do kosza, gdyż nie da się już go odwiesić (funkcja ResumeThread wywołana z innego wątku nic tu nie pomoże).
    Również przyjście komunikatu okna w czasie, gdy nasz wątek smacznie śpi, nic nie zmienia. Wiadomości takie będą nieobsłużone aż do odwieszenia się wątku. Wynika stąd fakt, że uśpienie na dłuższy czas wątku, w którym obsługujemy UI, będzie dla użytkownika natychmiast zauważalne jako zawieszenie się programu. Jeśli chcemy, by w czasie przerwy aplikacja odpowiadała na komunikaty, należy zastosować inną funkcję (np. MsgWaitForMultipleObjectsEx albo po prostu GetTickCount wraz z wewnętrzną pętlą komunikatów).
    Istnieje też wariant Ex funkcji Sleep. Różni się on od oryginału tym, że rozpoczęte przez niego oczekiwanie można przerwać, jeśli sobie tego zażyczymy. Wątek uśpiony przez SleepEx może być przedwcześnie obudzony, gdy otrzyma informacje o zakończeniu asynchronicznej operacji I/O lub asynchronicznego wywołania procedury (APC).
  2. Czas, na jaki faktycznie uśpimy nasz wątek, będzie prawie na pewno dłuższy niż ten, który podamy jako parametr funkcji Sleep. Jest on bowiem determinowany przez długość tzw. kwantów czasu (time slices), jakie system operacyjny przydziela kolejnym wątkom, by zapewnić złudzenie ich jednoczesnego wykonania. Działanie Sleep polega w rzeczywistości na oddaniu systemowi reszty kwantu czasu, który został przydzielony wątkowi; przestawienie wątku w stan “nieuruchamialności” na podaną ilość milisekund; a następnie na wznowieniu jego pracy, gdy ponownie otrzyma czas procesora od systemowego schedulera. W sumie więc czas uśpienia będzie równy:

    PozostałyKwantCzasu + ParametrSleep + CzasDoUaktywnieniaWątku

    Stąd wynikają dwa wnioski. Po pierwsze, nie powinniśmy nigdy używać Sleep jako sposobu na mierzenie czasu – już poczciwy GetTickCount sprawi się tu znacznie lepiej. Po drugie, wywołanie Sleep(0); jest jak najbardziej dopuszczalne i oznacza przedwczesne zrzeczenie się kwantu czasu, jaki wątek dostał od systemu. W czasach 16-bitowych wersji Windows i wielowątkowości bez wywłaszczała była od tego specjalna funkcja Yield, którą należało często wywoływać, aby przełączanie wątków w ogóle było możliwe. Teraz rzecz jasna nie jest to konieczne, ale nadal może być przydatne dla zasygnalizowania, że nasz wątek nie robi nic pożytecznego, a tylko w brzydki sposób na coś czeka (tzw. busy waiting).

O tych dwóch szczegółach odnośnie funkcji Sleep dobrze jest pamiętać, jeśli nasze wątki chcemy usypiać. Jako programiści Windows możemy się aczkolwiek podbudować tym, że nie mamy przy tym takich problemów jak koderzy piszący pod Linuksem. Tam sleep może być potencjalnie zaimplementowany na sygnałach, co wymaga ostrożności przy stosowaniu go razem z funkcjami alarm i signal.

Tags: ,
Author: Xion, posted under Programming » Comments Off on Dwie uwagi o śpiących wątkach

Normalne w terenie

2009-11-24 22:23

Siatka terenuParę dni temu przyszło mi w końcu napisać coś, co wielu programistów grafiki pewnie już nie jeden raz miało okazję kodować. Chodzi o generowanie siatki terenu na podstawie predefiniowanej mapy wysokości (heightmap), zapisanej w pliku jako obrazek w odcieniach szarości.

Cała procedura nie jest bardzo skomplikowana. W sumie sprowadza się ona do równomiernego próbkowania obrazka mapy i tworzenia wierzchołków leżących na ustalonej płaszczyźnie, z uwzględnieniem odczytanej wysokości.
Pozycje tych wierzchołków wyznaczyć jest prosto, chociaż zależy to trochę od przyjętej reprezentacji płaszczyzny (a właściwie prostokąta) “poziomu morza”. Trochę więcej zabawy jest natomiast z wektorami normalnymi, które niewątpliwie przydadzą się, jeśli nasz teren będziemy chcieli oświetlić. Właśnie o ich znajdowaniu chciałem napisać.

Jak wiadomo, wierzchołki dowolnej siatki możemy wyposażyć w normalne, posługując się niezwykle przydatną operacją iloczynu wektorowego. Przy jego pomocy można obliczyć normalne dla poszczególnych trójkątów; w przypadku wierzchołków należy po prostu uśrednić wyniki dla sąsiadujących z nimi face‘ów (oznaczonych niżej jako T(v)):

\displaystyle n(v) = \frac{1}{|T(v)|} \sum_{(a,b,c) \in T(v)}{(b-a) \times (c-a)}

Konieczną normalizację niektórzy przeprowadzają tu na końcu, a inni dla poszczególnych trójkątów. Prawdę mówiąc nie wiem, które podejście jest właściwsze – jeśli którekolwiek.

W powyższy sposób można oczywiście wyliczać normalne również dla utworzonego terenu, bo przecież czym on jest, jak właśnie siatką :) Jednak w tym przypadku mamy dostęp do większej liczby informacji o nim. Mamy przecież źródłową mapę wysokości, z której na wierzchołki przerobiliśmy tylko niektóre piksele (plus ew. jakieś ich otoczenia). Czemu by nie wykorzystać jej w większym stopniu, generując być może lepsze przybliżenie normalnych?
Ano właśnie, dlaczego nie :) W tym celu można by wprowadzić nieco wyższej (dosłownie) matematyki i zauważyć, że nasza heightmapa jest zbiorem wartości pewnej funkcji z = f(x,y) i że wobec tego normalną w jakimś punkcie x0, y0 da się wyliczyć jako:

\displaystyle \frac{\partial z}{\partial x}(x_0, y_0) \times \frac{\partial z}{\partial y}(x_0, y_0)

o ile tylko rzeczone pochodne istnieją. Można by – ale przecież nie będziemy tego robili ;-) Zamiast tego wystarczy zastanowić się, co by było, gdybyśmy wygenerowali skrajnie gęstą siatkę dla naszego terenu: tak gęstą, że każdemu pikselowi heightmapy odpowiadałby dokładnie jeden wierzchołek tej siatki. Wówczas opisana wyżej metoda liczenia normalnych korzystałaby ze wszystkich informacji zawartych w mapie.
Nie musimy jednak generować tych wszystkich wierzchołków. Do obliczenia wektora normalnego w punkcie wystarczą tylko dwa, odpowiadające – na przykład – pikselowi heightmapy położonemu bezpośrednio na prawo i u dołu tego, z którego “wzięliśmy” dany wierzchołek siatki. Z tych trzech punktów możemy następnie złożyć trójkąt, obliczyć wektor normalny i zapisać go w wierzchołku siatki:

n(v) = (\mathit{pos3d}(p_{x+1,y}) - v) \times (\mathit{pos3d}(p_{x,y+1}) - v) \\ \text{gdzie} \quad v = \mathit{pos3d}(p_{x,y})

Tutaj p_{x,y} oznacza odpowiedni piksel mapy wysokości, a funkcja pos3d jest tą, która dla owego pikseli potrafi wyliczyć pozycję odpowiadającego mu wierzchołka w wynikowej siatce. (Taką funkcję mamy, bo przecież jakoś generujemy tę siatkę, prawda? :])

Wektor normalnyZ podanych sposobów obliczania normalnych terenu można oczywiście korzystać niezależnie od tego, z jaką biblioteką graficzną pracujemy. Jak to jednak często bywa, w DirectX sporo rzeczy mamy zaimplementowanych od ręki w postaci biblioteki D3DX i nie inaczej jest z liczeniem normalnych.
I tak funkcja D3DXComputeNormals potrafi wyliczyć wektory normalne dla dowolnej siatki – warunkiem jest to, żeby była ona zapisana w postaci obiektu ID3DXMesh, więc w razie potrzeby musielibyśmy takowy obiekt stworzyć. Z kolei D3DXComputeNormalMap potrafi stworzyć mapę normalnych na podstawie mapy wysokości; tę pierwszą możemy później indeksować w celu pobrania “wektorów normalnych pikseli”.

Pule pamięci w DirectX

2009-11-21 12:24

Jedną z rzeczy, która na początku programowania w DirectX może wydawać się dziwna, jest tajemniczy parametr Pool. Pojawia się on w każdej funkcji, która tworzy jakiś zasób graficzny: teksturę, bufor wierzchołków, siatkę modelu (ID3DXMesh), bufor głębokości, itp.
Rolą tego parametru jest określenie, w której puli pamięci znajdzie się tworzony zasób. DX wyróżnia bowiem ich kilka, co jest związane przede wszystkim z (przynajmniej) dwoma rodzajami pamięci, z jakimi możemy mieć do czynienia programując grafikę: zwykłym systemowym RAM-em oraz pamięcią karty graficznej.

W jakiej puli powinniśmy więc umieszczać swoje obiekty? To zależy od kilku czynników. Na początek na pewno warto przyjrzeć możliwościom:

  • D3DPOOL_DEFAULT, jak wskazuje na to nazwa, oznacza pulę domyślną. Gdy użyjemy tej flagi, DirectX umieści nasz zasób w najlepszym – pod względem wydajności – miejscu, bazując przy tym na innym parametrze, Usage (określa on, mówiąc w skrócie, sposób wykorzystania danego zasobu). Tym najlepszym miejscem jest prawie zawsze pamięć karty graficznej.
    To sprawia jednak, że przy utracie urządzenia (o ile nie programujemy co najmniej w DX9Ex) taki zasób należy zwolnić, a potem utworzyć ponownie – musimy więc pamiętać sposób, w jaki go utworzyliśmy. Ponadto istnieją też pewne ograniczenia (bezwzględne lub wydajnościowe) w dostępie do obiektów z puli domyślnej: o ile nie zadeklarujemy ich jako dynamiczne (D3DUSAGE_DYNAMIC), ich blokowanie wiąże się ze stratą szybkości albo jest wręcz niemożliwe (w przypadku tekstur).
  • D3DPOOL_MANAGED to pula zarządzana. Oznacza to, że pieczę nad nią sprawują sam DirectX i to on decyduje, w którym rodzaju pamięci zasoby z tej puli zostaną umieszczone. Zazwyczaj oznacza to, że w pamięci operacyjnej trzymana jest kopia obiektu, znajdującego się też w pamięci graficznej. Dzięki temu nie trzeba go tworzyć ponownie w przypadku straty urządzenia, a także można go blokować i modyfikować niezależnie od typu i flag Usage; w tym przypadku DX zadba o odpowiednią synchronizację.
  • D3DPOOL_SYSTEMMEM oznacza pulę pamięci systemowej. Zasoby tu stworzone będą znajdowały się w zwykłym RAM-ie i nie będą mogły być bezpośrednio renderowane. Dane z nich mogą jednak być kopiowane do zasobów znajdujących się w puli domyślnej, jak chociażby poprzez funkcję UpdateTexture.
  • D3DPOOL_SCRATCH jest również usytuowana w pamięci systemowej (RAM). W odróżnieniu od poprzedniej, zasoby z tej puli nie są jednak w żaden sposób (także pośredni) dostępne dla urządzenia i nie mogą być używane podczas renderowania. Oznacza to też, że nie podlegają one ograniczeniom związanym z kartą graficzną. Można więc, przykładowo, tworzyć w tej puli tekstury o rozmiarach niebędących potęgami dwójki także wtedy, gdy karta nie wspiera takich tekstur.

Spotkałem się z dwiema ogólnymi wytycznymi dotyczącymi tego, z których pul pamięci należy korzystać:

  1. Pierwsza z nich mówi, że należy wszystko, co się da, umieszczać w D3DPOOL_DEFAULT, bo to zapewnia największą wydajność, jako że wtedy zasoby generalnie umieszczane są w pamięci karty graficznej.
  2. Druga szkoła sugeruje z kolei, żeby wszystko, co się da, umieszczać w D3DPOOL_MANAGED, gdyż wtedy pozwalamy DirectX-owi zdecydować, w jakim rodzaju pamięci trzymać nasz zasób – a już on powinien o tym wiedzieć lepiej.

W sumie więc wygląda na to, że nic nie wiadomo :) Oczywiście są przypadki, gdy wyboru nie mamy, jak to się dzieje choćby dla tekstur typu render target, które muszą być stworzone w D3DPOOL_DEFAULT. Zawsze jednak będziemy mieli takie zasoby, które “równie dobrze” dałoby się umieścić w puli domyślnej, jak i zarządzanej. Co wtedy?
Otóż wydaje mi się, że to zależy od wielkości naszej aplikacji (w sensie rozmiaru używanych zasobów) oraz… stopnia zaawansowania w programowaniu w DirectX. Na początek bowiem D3DPOOL_MANAGED jest pulą bezpieczniejszą: nie musimy się w niej martwić o stratę urządzenia i możemy każdy zasób blokować i zmieniać. Ceną za to jest wzrost zużycia pamięci przez aplikację, spowodowany trzymaniem przez DX kopii obiektów w pamięci systemowej.
Jeśli jednak nasza gra (nie bójmy się użyć tego słowa ;)) używa dużej ilości zasobów, to takie marnotrawstwo jest zwykle nie do przyjęcia. Wtedy przynajmniej część z nich należy przenieść do D3DPOOL_DEFAULT. Istnieje szansa, że na tym etapie będziemy już wiedzieć lepiej, którą część ;-)

Na koniec pozwolę sobie też wspomnieć o – często pomijanej – puli D3DPOOL_SCRATCH. Jej przeznaczeniem są wszelkiego rodzaju zasoby pomocnicze, których nie renderujemy, ale mimo to wykorzystujemy do jakiegoś celu – na przykład tworzenia innych zasobów. Typowym przykładem są wszelkiego rodzaju pomocnicze, narzędziowe tekstury – jak choćby mapy wysokości (heightmap), na podstawie których generujemy ukształtowanie terenu.
Najlepszą pulą dla takich obiektów jest właśnie D3DPOOL_SCRATCH. Użycie jakiejkolwiek innej spowodowałoby uszczerbek na wydajności lub wręcz błędy, jak np. niezamierzone przeskalowanie tekstury do rozmiaru 2n.

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


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