Za sprawą przedmiotu o nazwie Grafika Komputerowa 3D musiałem ostatnio przypomnieć sobie, jak tak naprawdę i w praktyce koduje się w DirectX. Pewnie brzmi to dziwnie, ale w rzeczywistości przez ładnych kilka miesięcy nie pisałem większych ilości kodu, który by z tej biblioteki korzystał.
Projekt, który musiałem teraz napisać, nie był ani trochę ambitny, bo polegał li tylko na wyświetleniu zupełnie statycznej sceny z kilkoma modelami, oświetleniu jej i zapewnieniu możliwości poruszania się w stylu strzelanek FPP. Oczywiście nie było też mowy o żadnych shaderach.
Niby banalne, ale jednak rzecz zajęła mi w sumie jakieś cztery znormalizowane wieczory (czyli od 3 do 4 godzin każdy). Częściowo było tak pewnie dlatego, że pokusiłem się jeszcze o teksturowanie, możliwość regulacji paru opcji renderowania czy bardzo, bardzo prosty menedżer sceny – czytaj: drzewko obiektów + stos macierzy ;)
Wydaje mi się jednak, że ważniejszą przyczyną był nieszczęsny fixed pipeline, którego byłem zmuszony używać. Jeszcze kilka lat temu nigdy bym nie przypuszczał, że to powiem, ale… shadery są po prostu łatwiejsze w użyciu. Porównując chociażby trywialne mieszanie koloru diffuse wierzchołka z teksturą przy użyciu pixel shadera:
oraz stanów urządzenia:
nietrudno jest ocenić, w której znacznie lepiej widać, co faktycznie dzieje się z kolorem piksela. No, chyba że dla kogoś multum stałych w rodzaju D3DABC_SOMESTRANGEOPTION
jest czytelniejsze niż po prostu mnożenie ;P
Inną sprawą jest też to, że w DirectX napisanie aplikacji od podstaw jest stosunkowo pracochłonne. Brak “złych” funkcji typu glVertex*
ma rzecz jasna swoje zalety, lecz jest też jednym z powodów, dla których tak opłaca się posiadanie własnego frameworka, a może nawet i – tfu tfu – silnika ;-)
Zgodnie z zasadami programowania obiektowego pola klas nie powinny być bezpośrednio dostępne na zewnątrz. Należy jest zawsze opakowywać w akcesory: właściwości lub krótkie metody typu get i set. Z nich właśnie korzysta potem kod zewnętrzny, dzięki czemu nie może on (w dobrze napisanej klasie) niczego zepsuć poprzez – chociażby – ustawienie jakiegoś pola na nieprzewidzianą wartość.
Taka praktyka jest powszechnie przyjęta i raczej nie budzi wątpliwości. Inaczej jest z używaniem tychże pól lub akcesorów wewnątrz klasy, a więc w jej własnych metodach. Tutaj często mamy wybór: czy odwołać się do “gołego” pola, czy też poprzez odpowiednią właściwość/metodę.
Które podejście jest właściwsze? C#/.NET od wersji 3.0 zdaje się to rozstrzygać, umożliwiając półautomatyczne tworzenie właściwości:
Nie ma tutaj nie tylko bloków get
i set
, ale i ukrytą pod tą właściwością pola. Przy korzystaniu z tego feature‘a żadnego dylematu więc nie ma.
Wydaje mi się jednak, że wybór nie jest taki oczywisty i że niekoniecznie należy używać akcesorów wewnątrz klasy. Argumentem przeciw, który od razu przychodzi do głowy, jest troska o wydajność – zwykle jednak przesadzona, bo proste gettery i settery są bez problemu rozwijane w miejscu użycia. Drugim ‘ale’ jest wygląd kodu w językach bez właściwości; zwłaszcza dotyczy to Javy, w której odpowiednik C#-owego:
roiłby się od get
ów. W końcu można by się jeszcze pokusić o uzasadnienie na wpół merytoryczne: skoro bądź co bądź prywatne pole jest składnikiem klasy do jej wyłącznej dyspozycji, to dlaczego metody miałyby obchodzić je dokoła zamiast odwoływać się doń bezpośrednio? A może jednak lepiej jest skorzystać z tej dodatkowej warstwy pośredniczącej (mogącej np. wykrywać jakieś błędy)?…
Na razie – mimo całkiem przyzwoitego doświadczenia w programowaniu w językach wszelakich – trudno jest mi na te pytania odpowiedzieć. Ostatnio aczkolwiek skłaniam się ku bezpośredniemu dostępowi do pól w metodach klas. Chętnie poznałbym jednak opinie innych koderów na ten temat.
Jeśli oprócz systemu okienkowego rodem z Microsoftu mamy też jakiegoś *niksa, to reinstalacja Windows będzie dla nas miała jeden nieprzyjemny efekt uboczny. Otóż instalatory Okienek radośnie nadpisują sektor startowy dysku (czyli MBR – Master Boot Sector), przez co Windows staje się jedynym systemem dającym się uruchomić w zwykły sposób. Ot, zwyczajowe MS-owe praktyki monopolistyczne ;-)
Jak temu zaradzić? Trzeba oczywiście przywrócić boot sector do właściwego stanu, co oznacza ponowne zainstalowanie używanego przez nas wcześniej bootloadera. W większości przypadków (jeśli mówimy o Linuksie jako drugim systemie) jest nim GRUB; w takim wypadku jego ponowne zainstalowanie wymaga:
Tyle powinno wystarczyć, by po ponownym uruchomieniu komputera z tego dysku pojawiło się nam menu bootowania GRUB-a. Przy odrobinie szczęścia oba systemy będą więc uruchamiały się normalnie ;)
Wczorajsza premiera Windows 7 to dobry pretekst, żeby nowy system w końcu przetestować – zwłaszcza, że większość opinii, których o nim słyszałem, była zdecydowanie przychylna. W połączeniu z faktem, iż system operacyjny na moim laptopie już od dobrych paru miesięcy domaga się skrócenia swoich cierpień, otrzymujemy tylko jeden logiczny wniosek: czas zakasać rękawy i zabrać się za reinstalację!
Ktokolwiek choć raz zajmował się ponownym stawianiem systemu od zera wie, że czynność ta nie należy do relaksujących. Chociaż drobne komplikacje są praktycznie gwarantowane: a to zapomnimy o jakimś sterowniku, a to zapodziejemy gdzieś numer seryjny systemu, i tak dalej. Posiadanie komputera “zapasowego” (w moim przypadku tradycyjnego – stacjonarnego) znacznie redukuje dolegliwość takich problemów, ale nawet i w tej sytuacji warto się do całej operacji dobrze przygotować.
I właśnie dlatego sporządziłem poniższą listę kontrolną czynności, które dobrze jest wykonać przed rozpoczęciem zabawy w reinstalację systemu. Nie gwarantuję oczywiście, że da się przy jej użyciu uniknąć wszelkich kłopotów. Powinna być ona jednak w dużym stopniu pomocna. A wygląda ona następująco:
Pamiętaj też o wszelkich kluczach produktów czy numerach seryjnych, jeśli któryś z systemów ich wymaga.
Uff, spora ta lista. Jej rygorystyczne przestrzeganie może nie zawsze jest konieczne, ale nie wydaje mi się, żeby mogło komukolwiek zaszkodzić :) Akurat w przypadku tej nieczęsto (i coraz rzadziej) wykonywanej czynności, jaką jest reinstalacja systemu, zbytnia przezorność na pewno nie zawadzi.
Zorientowanie się w dużym pliku z kodem (gdzie przez ‘duży’ rozumiem przynajmniej taki, który przekracza tysiąc linii) może niekiedy przysparzać kłopotów. W IDE są oczywiście narzędzia nawigacyjne, pozwalające na przejście do poszczególnych klas, metod czy deklaracji, o ile tylko znamy chociaż ich nazwy. Nie zawsze jednak tak jest. Jeśli o danej metodzie pamiętamy tylko to, że “była długa i skomplikowana”, a o jakiejś właściwości jedynie tyle, iż “jest gdzieś wśród parunastu innych deklaracji”, to najpewniej oznacza, że w ich poszukiwaniu będziemy musieli przeglądnąć cały plik od początku do końca.
Chyba że… No właśnie – chyba że dałoby się spojrzeć na kod z daleka, by zobaczyć jego ogólną strukturę. Wiadomo bowiem, że metoda “długa i skomplikowana” będzie miała najpewniej sporą ilość wcięć, a długi ciąg deklaracji, jedna pod drugą, też były łatwy do odróżnienia od innych kawałków kodu. W książce o wiele mówiącej nazwie Czytanie kodu znalazłem kiedyś radę, że do uzyskania takiego ogólnego spojrzenia można wykorzystać edytor tekstu typu Word, pozwalający na podgląd wydruku wielu stron naraz.
O wiele wygodniej byłoby jednak mieć podobną możliwość wprost w IDE. NetBeans posiada namiastkę czegoś takiego, jednak za jej pomocą można tylko szybko stwierdzić, gdzie w kodzie znajdują się błędy kompilacji (pisałem zresztą o tym trochę ponad rok temu). Porządną, wielkoskalową, a w dodatku całkiem funkcjonalną “mapę kodu” da się za to znaleźć w… Visual Studio.
Mówię tu o darmowym pluginie o nazwie RockScroll, będącym zresztą początkowo wewnętrznym narzędziem Microsoftu. Tym, co wtyczka ta robi, jest zastąpienie standardowego pionowego paska przewijania przez szerszy pionowy pasek, pokazujący podgląd aktualnie edytowanego w postaci długiej “miniaturki” z kolorowaną składnią. RockScroll działa przy tym podobnie jak zwyczajny pasek przewijania, a więc pozwala na przejście kliknięciem do wybranego miejsca w pliku. Ponadto potrafi też zaznaczać breakpointy oraz koloruje wszystkie wystąpienia wskazanego (dwukrotnie klikniętego) słowa w danym pliku – całkiem przydatne. Jedynym mankamentem jest chyba tylko brak wsparcia dla zwijanych i rozwijanych regionów kodu oraz ewentualnie fakt, że w poziomie plugin zajmuje jakieś cztery razy więcej miejsca niż standardowy pasek przewijania. Na szerokoekranowych monitorach ciężko jednak uznać to za wadę :)
W pewnych sprawach kiedyś występowała alternatywa dwóch równoważnych możliwości i trzeba było w końcu zdecydować się na wybór jednej z nich. Matematycy często ustalają w ten sposób coś “dla porządku” lub dla tzw. ustalenia uwagi. Jak na ironię zauważyłem jednak, że zwykle to właśnie w matematyce niektóre powszechnie obowiązujące umowy wcale nie wprowadzają porządku, gdyż są dokładnie odwrotne względem intuicji lub codziennego doświadczenia. Oto przykłady:
Funkcje rzeczywiste nazywane wypukłymi narysowane w postaci wykresu przyjmują postać krzywej wygiętej do dołu, co sugeruje nazywać je raczej… wklęsłymi (obrazuje to rysunek po prawej).
Na pewno nie są to wszystkie przypadki podobnych “niefortunnych” rozstrzygnięć; z pewnością dałoby się znaleźć ich więcej. Na pewno też każdy z nich daje się w zadowalający sposób uzasadnić (jak chociażby przekątną macierzy – jest ona po prostu definiowana przez te komórki, których numer wiersza jest równy numerowi kolumny). I paradoksalnie to właśnie jest w nich najgorsze: nie da się z nimi nic zrobić, jak tylko zwyczajnie zapamiętać :)
W prawie wszystkich językach obiektowych istnieją tak zwane specyfikatory dostępu, przykładem których jest choćby public
czy private
. Pozwalają one ustalać, jak szeroko dostępne są poszczególne składniki klas w stosunku do reszty kodu. Modyfikatory te występują m.in. w trzech najpopularniejszych, kompilowalnych językach obiektowych: C++, C# i Javie.
Problem polega na tym, że w każdym z nich działają one inaczej. Mało tego: każdy z tych języków posiada inny zbiór tego rodzaju modyfikatorów. Dla kogoś, komu zdarza się pisać regularnie w przynajmniej dwóch spośród nich, może to być przyczyną pomyłek i nieporozumień.
Dlatego też przygotowałem prostą ściągawkę w postaci tabelki, którą prezentuję poniżej. Symbol języka występujący w komórce oznacza tutaj, że dany modyfikator (z wiersza) powoduje widoczność składnika klasy w określonym miejscu (z kolumny).
Specyfikator | Klasa | Podklasa | Pakiet | Reszta |
public |
C++ C# Java | C++ C# Java | C# Java | C++ C# Java |
protected |
C++ C# Java | C++ C# Java | Java | |
protected internal |
C# | C# | C# | |
internal |
C# Java | C# Java | ||
private |
C++ C# Java |
Parę uwag gwoli ścisłości:
public
, jako że w tym języku w ogóle nie istnieje pojęcie pakietu.internal
jest słowem specyficznym dla C#, a jego ekwiwalentem w Javie jest po prostu pominięcie modyfikatora w deklaracji (np. int x;
zamiast internal int x;
).protected internal
jest modyfikatorem występującym wyłącznie w C#/.NET.