{"id":637,"date":"2010-12-26T17:33:42","date_gmt":"2010-12-26T16:33:42","guid":{"rendered":"http:\/\/netrix.org.pl\/index.php\/2010\/12\/26\/nine3-zalozenia-i-struktura-silnika\/"},"modified":"2010-12-26T17:33:42","modified_gmt":"2010-12-26T16:33:42","slug":"nine3-zalozenia-i-struktura-silnika","status":"publish","type":"post","link":"https:\/\/netrix.org.pl\/index.php\/2010\/12\/26\/nine3-zalozenia-i-struktura-silnika\/","title":{"rendered":"NIne3 &#8211; za\u0142o\u017cenia i struktura silnika"},"content":{"rendered":"<p>Witam ponownie. Tym razem napisz\u0119 nieco o za\u0142o\u017ceniach i strukturze mojego silnika, kt\u00f3rym ma docelowo by\u0107 NIne3. Numerek &#8216;3&#8217; \u015bwiadczy o tym, \u017ce jest to ju\u017c trzecia iteracja, zatem jest to pewna zmiana w stosunku do wcze\u015bniejszych post\u00f3w. Kod zosta\u0142 przepisany na nowo, zmieni\u0142y si\u0119 r\u00f3wnie\u017c za\u0142o\u017cenia, ale cz\u0119\u015b\u0107 z nich oraz r\u00f3\u017cne fragmenty kodu zosta\u0142y wykorzystane ponownie. Musz\u0119 przyzna\u0107, \u017ce jestem zadowolony z aktualnego stanu kodu i my\u015bl\u0119, \u017ce ta iteracja b\u0119dzie jedn\u0105 z tych d\u0142u\u017cszych i dotrwa do czego\u015b wi\u0119kszego :).<\/p>\n<h3>Za\u0142o\u017cenia<\/h3>\n<p>Podstawowe za\u0142o\u017cenia to:<\/p>\n<ul>\n<li>brak wyj\u0105tk\u00f3w <\/li>\n<li>brak STL (strumienie, kontenery, <em>std::string<\/em>) <\/li>\n<li>brak funkcji wirtualnych (przynajmniej na niskim poziomie) <\/li>\n<li>obiektowo\u015b\u0107 <\/li>\n<li>modu\u0142owo\u015b\u0107 <\/li>\n<li>ca\u0142kowite ukrycie wykorzystywanego API graficznego <\/li>\n<li>oddzielenie klas implementacyjnych od interfejsu programistycznego <\/li>\n<li>minimalizacja alokacji pami\u0119ci <\/li>\n<li>bezpieczny kod <\/li>\n<\/ul>\n<p>Dlaczego takie za\u0142o\u017cenia? Ot\u00f3\u017c, po pierwsze, wyj\u0105tki, mimo \u017ce jest to bardzo wygodny feature j\u0119zyka C++, s\u0105 do\u015b\u0107 kosztowne, poniewa\u017c wymagaj\u0105 du\u017co pracy w\u0142o\u017conej w napisanie kodu, kt\u00f3ry zwolni wszystkie zasoby i sam przy tym nie wygeneruje b\u0142\u0119d\u00f3w. Z drugiej strony r\u00f3\u017cne kompilatory r\u00f3\u017cnie implementuj\u0105 obs\u0142ug\u0119 wyj\u0105tk\u00f3w, wi\u0119c nie jest do ko\u0144ca pewne czy nie pojawi si\u0119 dodatkowy narzut zwi\u0105zany z obs\u0142ug\u0105 powrotu z funkcji wewn\u0105trz bloku <em>try\/catch<\/em>.<\/p>\n<p>Natomiast STL mimo, \u017ce jest bibliotek\u0105 napisan\u0105 w najwydajniejszy mo\u017cliwy spos\u00f3b (a przynajmniej powinna by\u0107) i jednocze\u015bnie jest do\u015b\u0107 elastyczna, jest do\u015b\u0107 obszerna i na start dorzuca nieca\u0142e p\u00f3\u0142 megabajta danych do pliku .exe, z czego z wi\u0119kszo\u015bci i tak si\u0119 nie korzysta bezpo\u015brednio. Ostatecznie nie spe\u0142nia ona r\u00f3wnie\u017c podstawowego za\u0142o\u017cenia jak\u0105 jest brak obs\u0142ugi wyj\u0105tk\u00f3w, kt\u00f3r\u0105 mo\u017cna co prawda wy\u0142\u0105czy\u0107, ale nie jest to \u0142atwe. Dlatego strumienie zosta\u0142y u mnie zast\u0105pione star\u0105, dobr\u0105 funkcj\u0105 <i>printf<\/i>, kontener <i>std::vector<\/i> w\u0142asn\u0105 implementacj\u0105 uwzgl\u0119dniaj\u0105c\u0105 jeden z punkt\u00f3w o minimalnej ilo\u015bci alokacji, a z <i>std::string<\/i> zrezygnowa\u0142em ca\u0142kowicie, poniewa\u017c nie jest ona konieczna, a poza tym dodatkowo nie spe\u0142niaj\u0105 kilku z wymienionych punkt\u00f3w.<\/p>\n<p>Brak funkcji wirtualnych na niskim poziomie wzi\u0105\u0142 si\u0119 z oczywistego narzutu w postaci <em>vtable<\/em>, zatem wykorzystywany interfejs zosta\u0142 napisany z u\u017cyciem PIMPL, dzi\u0119ki czemu mo\u017cliwa jest optymalizacja i inline&#8217;owanie funkcji (na poziomie konsolidacji). Nie wykluczam natomiast u\u017cycia ich w przysz\u0142o\u015bci na wy\u017cszym poziomie kodu, ale p\u00f3ki co staram si\u0119 je omija\u0107.<\/p>\n<p>Obiektowo\u015b\u0107 wi\u0105\u017ce si\u0119 przede wszystkim z enkapsulacj\u0105 kodu, co pozwala oddzieli\u0107 kod obs\u0142ugi tekstur od kodu obs\u0142ugi okna, efekt\u00f3w etc. w spos\u00f3b wyra\u017any. Dzi\u0119ki temu kod jest przejrzysty i \u0142atwy w modyfikowaniu. Ten punkt powi\u0105zany jest bezpo\u015brednio z punktem nast\u0119pnym czyli modu\u0142owo\u015bci\u0105. Chcia\u0142bym, \u017ceby kod by\u0142 \u0142atwo rozszerzalny i umo\u017cliwia\u0142 wymian\u0119 r\u00f3\u017cnych klas oraz ich dodawanie, bez modyfikowania pozosta\u0142ych. Poka\u017c\u0119 to ni\u017cej na przyk\u0142adzie hierarchii modu\u0142\u00f3w.<\/p>\n<p>W tej iteracji postanowi\u0142em r\u00f3wnie\u017c ukry\u0107 ca\u0142kowicie wykorzystywane API graficzne, kt\u00f3rym p\u00f3ki co nadal jest Direct3D 9. API jest ukryte nie tylko na zewn\u0105trz biblioteki, ale r\u00f3wnie\u017c wewn\u0105trz dla modu\u0142\u00f3w wy\u017cszego rz\u0119du, kt\u00f3re nie korzystaj\u0105 bezpo\u015brednio z klas implementacyjnych, ale z interfejsu u\u017cytkownika, co zapewnia niezale\u017cno\u015b\u0107.<\/p>\n<p>Docelowy u\u017cytkownik biblioteki nie widzi bezpo\u015brednio klas implementacyjnych tylko klasy interfejsowe napisane przy u\u017cyciu PIMPL, kt\u00f3re s\u0105 uporz\u0105dkowane w odpowiedniej hierarchii, kt\u00f3r\u0105 poka\u017c\u0119 nieco ni\u017cej. Daje to swobod\u0119 dzia\u0142ania wewn\u0105trz silnika i uporz\u0105dkowanie na zewn\u0105trz, jest r\u00f3wnie\u017c mniej b\u0142\u0119dogenne.<\/p>\n<p>Ostatnie dwa punkty s\u0105 niejako powi\u0105zane, poniewa\u017c zak\u0142adam uruchamianie silnika na maszynach z ograniczon\u0105 ilo\u015bci\u0105 pami\u0119ci i mo\u017cliwo\u015b\u0107 wyst\u0105pienia b\u0142\u0119du alokacji pami\u0119ci, wi\u0119c obs\u0142uga takich b\u0142\u0119d\u00f3w jest wymagana. Natomiast w celu minimalizacji tego typu b\u0142\u0119d\u00f3w postanowi\u0142em zmniejszy\u0107 ilo\u015b\u0107 alokacji do minimum stosuj\u0105c kilka w\u0142asnych klas do tego celu (bez menad\u017cera pami\u0119ci p\u00f3ki co). Dodatkowo zak\u0142adam, \u017ce wszelkie mo\u017cliwe b\u0142\u0119dy wyst\u0105pi\u0105 w przypadku inicjalizacji i \u0142adowania zasob\u00f3w, wi\u0119c ka\u017cdy mo\u017cliwy b\u0142\u0105d zostaje przekazany do u\u017cytkownika biblioteki i zapisany w logu. Natomiast w czasie dzia\u0142ania aplikacji b\u0142\u0119dy nie powinny wyst\u0105pi\u0107&#160; albo b\u0119d\u0105 wyst\u0119powa\u0107 bardzo rzadko, w tym przypadku ich sprawdzenie, o ile to mo\u017cliwe, b\u0119dzie wyst\u0119powa\u0107 w jednym ustalonym miejscu.<\/p>\n<h3>Hierarchia interfejs\u00f3w<\/h3>\n<p>Jak ju\u017c wspomnia\u0142em wcze\u015bniej klasy implementacyjne s\u0105 oddzielone od u\u017cytkownika warstw\u0105 interfejsu, kt\u00f3ry ma za zadanie utworzy\u0107 odpowiedni\u0105 hierarchi\u0119 po to, aby klasy specjalizowane, zale\u017cne od og\u00f3lnych, nie mog\u0142y by\u0107 pobrane i u\u017cyte przed zainicjalizowaniem tamtych. Dodatkowo daje to mo\u017cliwo\u015b\u0107 zmian wewn\u0105trz silnika, bez zmiany samego interfejsu. Sama hierarchia wygl\u0105da nast\u0119puj\u0105co:<\/p>\n<ul>\n<li>ICore:\n<ul>\n<li>IWindowManager:\n<ul>\n<li>INInput <\/li>\n<\/ul>\n<\/li>\n<li>ITimer <\/li>\n<li>IRenderer:\n<ul>\n<li>IMeshManager <\/li>\n<li>ITextureManager <\/li>\n<li>IEffectManager:\n<ul>\n<li>IEffect <\/li>\n<\/ul>\n<\/li>\n<li>IModelLoader:\n<ul>\n<li>IModel:\n<ul>\n<li>IMaterial <\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<li>IMeshLoaderX <\/li>\n<li>IMeshLoaderHeightmap <\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Jak wida\u0107 podzia\u0142 jest do\u015b\u0107 wyra\u017any. Wyszczeg\u00f3lni\u0142em ca\u0142kowicie niezale\u017cne od siebie modu\u0142y: <em>Timer<\/em>, <em>WindowManager<\/em> i <em>Renderer<\/em>. Ka\u017cd\u0105 z nich mo\u017cna inicjalizowa\u0107 osobno lub zrobi\u0107 to za pomoc\u0105 klasy <em>Core<\/em>, kt\u00f3ra zainicjalizuje wszystkie modu\u0142y i udost\u0119pni kilka podstawowych funkcji takich jak: <em>Update()<\/em>, <em>Present()<\/em>, <em>ChangeDisplayMode()<\/em>, kt\u00f3re na nich operuj\u0105. <\/p>\n<p>Najwi\u0119kszym modu\u0142em jest <em>Renderer<\/em>, kt\u00f3ry r\u00f3wnie\u017c zosta\u0142 wyra\u017anie podzielony na kilka podmodu\u0142\u00f3w. Podstawowymi s\u0105 tak naprawd\u0119 <em>MeshManager<\/em>, <em>TextureManager<\/em> oraz <em>EffectManager<\/em> (shadery) i to te klasy s\u0105 odpowiedzialne za obs\u0142ug\u0119 podstawowych zasob\u00f3w niezb\u0119dnych do wyrenderowania czegokolwiek: siatek, tekstur oraz shader\u00f3w. Drug\u0105 grup\u0105 podmodu\u0142\u00f3w s\u0105 te odpowiedzialne za \u0142adowanie wymienionych zasob\u00f3w z plik\u00f3w w r\u00f3\u017cnych formatach \u2013 aktualnie obs\u0142uguj\u0119 tylko dwa &#8211; pliki .x oraz heightmapy. Ostatni\u0105 grup\u0105 s\u0105 podmodu\u0142y zarz\u0105dzaj\u0105ce, kt\u00f3rych przedstawicielem jest ModelManager. Jego zadaniem jest zebranie zasob\u00f3w w jednym miejscu i dodanie im odpowiedniej funkcjonalno\u015bci tak, aby by\u0142o mniej roboty przy renderowaniu.<\/p>\n<h3>Hierarchia modu\u0142\u00f3w<\/h3>\n<p>Sercem silnika jest hierarchia modu\u0142\u00f3w, kt\u00f3ra sk\u0142ada si\u0119 z klas bibliotecznych, struktur oraz klas silnika:<\/p>\n<ul>\n<li>Klasy biblioteczne:\n<ul>\n<li>NMath:\n<ul>\n<li>NMVector <\/li>\n<li>NMMatrix <\/li>\n<li>NMQuaternion <\/li>\n<\/ul>\n<\/li>\n<li>NAssert <\/li>\n<li>NLogger <\/li>\n<li>NDynamicTable <\/li>\n<li>NDataVector <\/li>\n<li>NStableDataVector <\/li>\n<li>Inne (mniejsze klasy implementuj\u0105ce r\u00f3\u017cn\u0105 funkcjonalno\u015b\u0107) <\/li>\n<\/ul>\n<\/li>\n<li>Struktury <\/li>\n<li>Engine:\n<ul>\n<li>Core <\/li>\n<li>WindowManager:\n<ul>\n<li>Input <\/li>\n<\/ul>\n<\/li>\n<li>Timer <\/li>\n<li>Renderer:\n<ul>\n<li>SubRenderer (Direct3D_9)\n<ul>\n<li>BuiltInLoader\n<ul>\n<li>MeshLoaderX <\/li>\n<\/ul>\n<\/li>\n<li>EffectManager <\/li>\n<li>MeshManager <\/li>\n<li>TextureManager <\/li>\n<\/ul>\n<\/li>\n<li>BuiltInLoaders\n<ul>\n<li>MeshLoaderHeightmap <\/li>\n<\/ul>\n<\/li>\n<li>ModelManager <\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>W klasach bibliotecznych mo\u017cna znale\u017a\u0107 definicje typ\u00f3w podstawowych, bibliotek\u0119 matematyczn\u0105, kontenery oraz klasy pomocne w debugowaniu (logger i asercja). Biblioteka matematyczna jest napisana z my\u015bl\u0105 o wykorzystaniu funkcji <em>intrinsic<\/em> procesora (SSE) i jej wygl\u0105d bazuje w bardzo du\u017cej mierze na <em>XNAMath<\/em>. Aktualnie obs\u0142ugiwane s\u0105 tylko wektory i macierze, ale nie wszystkie operacje zosta\u0142y zaimplementowane, s\u0105 tylko te aktualnie potrzebne. Mi\u0119dzy kontenerami mo\u017cna wyr\u00f3\u017cni\u0107 opakowan\u0105 w klas\u0119 zwyk\u0142a tablic\u0119 dynamiczn\u0105 (<em>NDynamicTable<\/em>), odpowiednik <em>std::vector<\/em> (<em>NDataVector<\/em>) oraz ten sam kontener, ale zachowuj\u0105cy wewn\u0119trzne u\u0142o\u017cenie danych (<em>NStableDataVector &#8211;<\/em> pomocne przy korzystaniu z uchwyt\u00f3w). <\/p>\n<p>Hierarchia silnika r\u00f3\u017cni si\u0119 to troch\u0119 od hierarchii interfejs\u00f3w, kt\u00f3r\u0105 implementuje. Przede wszystkim wyr\u00f3\u017cnia si\u0119 tutaj grupa SubRenderer. Jej celem jest zebranie wszystkich klas zale\u017cnych od API graficznego w jednym miejscu. Dzi\u0119ki temu mo\u017cliwa jest \u0142atwa wymiana tego API przy zachowaniu funkcjonalno\u015bci silnika. Wszystkie klasy, kt\u00f3re znajduj\u0105 si\u0119 poza t\u0105 grup\u0105 korzystaj\u0105 albo z klasy po\u015bredniej <em>SubRenderer<\/em>, albo z interfejs\u00f3w u\u017cytkownika. Zmiana API b\u0119dzie mo\u017cliwa tylko statycznie, za pomoc\u0105 odpowiedniej dyrektywy preprocesora. Klasy znajduj\u0105ce si\u0119 w tej grupie odpowiedzialne s\u0105 za zarz\u0105dzanie podstawowymi zasobami oraz samym urz\u0105dzeniem i nie robi\u0105 nic poza podstawowymi operacjami.<\/p>\n<p>Wszystkie zasoby s\u0105 udost\u0119pniane za pomoc\u0105 uchwyt\u00f3w, dzi\u0119ki kt\u00f3rym mo\u017cna \u0142atwo manipulowa\u0107 zasobami wewn\u0105trz silnika bez obawy, \u017ce co\u015b zostanie zgubione na zewn\u0105trz. Niekt\u00f3re z nich udost\u0119pniaj\u0105 dodatkowy interfejs (<em>Effect<\/em>, <em>Model<\/em>), kt\u00f3ry umo\u017cliwia bardziej szczeg\u00f3\u0142ow\u0105 manipulacj\u0119 danym zasobem, ale klasy wewn\u0119trzne Renderera manipuluj\u0105 wy\u0142\u0105cznie uchwytami. <\/p>\n<p>Opr\u00f3cz tego istnieje ca\u0142y zestaw pomocnych struktur, w tym w\u0142a\u015bnie szablon uchwyt\u00f3w, kt\u00f3re bazuj\u0105 na odpowiednich akcesorach. Dzi\u0119ki temu wszystko pozostaje hermetycznie zamkni\u0119te wewn\u0105trz silnika.<\/p>\n<h3><\/h3>\n<h3><\/h3>\n<h2><\/h2>\n<h5><\/h5>\n<h2><\/h2>\n<h3><\/h3>\n<h3>Co dalej?<\/h3>\n<p>Aktualnie zako\u0144czy\u0142em restrukturyzacj\u0119 hierarchii modu\u0142\u00f3w do postaci takiej jak\u0105 przedstawi\u0142em. Kolejnym moim celem jest dodanie <em>SceneGraph<\/em>, kt\u00f3ry b\u0119dzie zarz\u0105dza\u0142 drzewem modeli oraz przechowywa\u0142 ich macierze \u015bwiata i je aktualizowa\u0142. Chc\u0119 przy tym wykorzysta\u0107 poprawk\u0119 z tej <a href=\"http:\/\/www.google.pl\/url?sa=t&amp;source=web&amp;cd=1&amp;sqi=2&amp;ved=0CBYQFjAA&amp;url=http%3A%2F%2Fresearch.scee.net%2Ffiles%2Fpresentations%2Fgcapaustralia09%2FPitfalls_of_Object_Oriented_Programming_GCAP_09.pdf&amp;rct=j&amp;q=pitfall%20matrix%20cache%20bounding%20sphere&amp;ei=wGwXTazfOsvIswbXpojYDA&amp;usg=AFQjCNEFjXXhA6SniUcb0qSiSdzm-Dyfaw&amp;cad=rja\">prezentacji<\/a> , czyli zawrze\u0107 ca\u0142e drzewo macierzy w jednej tablicy, kt\u00f3ra b\u0119dzie cache-friendly.     <br \/>Opr\u00f3cz tego w zwi\u0105zku z wydzieleniem SubRenderera chcia\u0142bym doda\u0107 drugi, oparty o OpenGL, ale to ju\u017c b\u0119dzie wi\u0119kszy orzech do zgryzienia. Musz\u0119 przyzna\u0107, \u017ce sam jestem ciekaw wyniku :).    <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Witam ponownie. Tym razem napisz\u0119 nieco o za\u0142o\u017ceniach i strukturze mojego silnika, kt\u00f3rym ma docelowo by\u0107 NIne3. Numerek &#8216;3&#8217; \u015bwiadczy o tym, \u017ce jest to ju\u017c trzecia iteracja, zatem jest to pewna zmiana w stosunku do wcze\u015bniejszych post\u00f3w. Kod zosta\u0142 przepisany na nowo, zmieni\u0142y si\u0119 r\u00f3wnie\u017c za\u0142o\u017cenia, ale cz\u0119\u015b\u0107 z nich oraz r\u00f3\u017cne fragmenty kodu [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[65,179,10,87,180,120,68,117,53,22,67,116,98],"_links":{"self":[{"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/posts\/637"}],"collection":[{"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/comments?post=637"}],"version-history":[{"count":0,"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/posts\/637\/revisions"}],"wp:attachment":[{"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/media?parent=637"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/categories?post=637"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/netrix.org.pl\/index.php\/wp-json\/wp\/v2\/tags?post=637"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}