Avocado – podstawowa implementacja timerów

Stworzyłem proste implementacje klasy Timer, która jest kluczowa do działania większości programów - chodzi o obsługę przerwania VBlank i jej synchronizacji z timerem zliczającym HBlanki.

Niestety kod pisany "na teraz" kopie mnie teraz po tyłku - trzeba usiąść i rozplanować zarządzanie timingami i synchronizację poszczególnych modułów. Obecnie panuje założenie, że x cyklów GPU to y cyklów CPU. Metody aktualizujące liczniki wywoływane są praktycznie co cykl GPU.  Ułatwiało to napisanie kodu, ale ucierpi na tym wydajność i bardzo utrudni zmianę częstotliwości bazowej (zmiana z NTSC na PAL). Prawidłowa implementacja powinna pozwalać na uruchomienie timera dla x cyklów systemu, nadgonić straty, wywołać przerwania. To samo tyczy się GPU - aktualizacja stanu powinna być robiona tylko wtedy, gdy jest taka potrzeba a nie w każdym cyklu.

Przy symulacji działania modulu CDROM (który nie jest jeszcze napisany) BIOS renderuje menu:

BIOS renderuje menu

BIOS renderuje menu

Zrzut ekranu z maszyny

Zrzut ekranu z maszyny dla porównania

Opublikowano Avocado | Skomentuj

./configure && make && make install

Zarządzanie zależnościami to jedna z mniej przyjemnych części kodowania w C++.

Pisząc w Javie mamy Mavena (czy Gradle), Python ma pip, PHP – composer, Node – npm, etc., etc. Każde z tych narzędzi łączy prostota użycia. Dla PHP wystarczy wpisać composer install i wszystkie zależności zapisane w prostym pliku json są zaczytywane do projektu. W przypadku nowego projektu można dodawać zależności używając composer require <nazwa_paczki>. Repozytorium wszystkich paczek jest dostępne pod adresem https://packagist.org/, poszczególne paczki to po prostu podpięte repozytoria gitowe. Proste i przyjemne w użyciu. Pozostałe menadżery działają prawie identycznie.

Teraz jak to wygląda w przypadku C++:

Jeżeli używamy Linuksa może nas poratować systemowy manadżer pakietów. W przypadku Debiana wydajemy komendę apt-get install libsdl2-dev i automatycznie instalują się nam odpowiednie nagłówki i biblioteki wymagane przy użyciu SDL. OS X ma trochę gorzej, ale można się zaopatrzyć w Homebrew, który działa podobnie jak apt. Najgorzej ma Windows – przez wiele lat nie doczekał się menadżera z prawdziwego zdarzenia. Do dzisiaj instalując jakiś program musimy przedzierać się przez nie zawsze budzące zaufanie strony, przeklikiwać się przez instalator uważając przy okazji żeby nie zainstalować jakiegoś toolbara. Gdy mowa o bibliotekach sprawa wygląda nie lepiej – jeżeli twórca udostępnia skompilowaną bibliotekę pod nasz kompilator, dodatkowo z tymi ustawieniami jakich oczekujemy to super – wystarczy ją pobrać, wypakować i dodać do ścieżek przy kompilacji. Ale gdy dostępne są tylko źródła? Pobieramy, rozpakowujemy i przy odrobinie szczęścia znajdziemy tam projekt do Visuala który po odpaleniu i skompilowaniu wypluje naszą libkę. W 90% przypadków zastaniemy tam jedynie plik configure – na Windowsie na nic on się nie zda.

Teraz wystarczy sobie wyobrazić sytuacje gdy chcemy skompilować projekt używający wiele bibliotek, a każda z nich ma jeszcze swoje zależności i na dodatek używają swojego systemu do budowania. Koszmar.

 

Opiszę po krótce jakich rozwiązań można użyć i co wybrałem ja.

1. Shell/Batch – najprostsze rozwiązanie. Wykonuje serie komend potrzebną do wygenerowania projektu. Zdarzało mi się używać, ale ma raczej same wady – rekompilowany jest cały projekt a nie tylko te pliki które się zmieniły, ograniczenie do jednej platformy. W praktyce nie stosuje się.

2. Makefile – wystarcza do prostych projektów, gdzie wymagamy wygenerowanie binarki na podstawie kodu źródłowego. Dobrze napisana konfiguracja dba o to, żeby kompilowane były tylko zmodyfikowane pliki. Sam się kiedyś na tym złapałem, gdyż mój Makefile nie brał pod uwagę zmian w plikach nagłówkowych i przez długi czas szukałem rozwiązania pewnego buga – wystarczyło zrobić clean i zbudować program jeszcze raz. Większe projekty używają antycznego Autotools z dodatkiem dziwnego systemu makr M4, który generuje configure a ten z kolei Makefile. Obydwa pliki są nieedytowalne (przykładowy configure dma ponad 25k linii kodu w shellu). Dla Linuksowych narzędzi to zazwyczaj standardowy sposób kompilacji: ./configure && make && make install

3. Ninja – szybki system czasami zastępujący Make, służy jedynie do budowania projektu. Raczej nie pisze się bezpośrednio pliku konfiguracyjnego, a używa innych systemów do wygenerowania go (tak jak configure tworzy Makefile)

4. SCons – używa Pythona, nie widziałem wiele projektów które by go używały. Spotkałem się z opinią, że jest bardzo wolny.

5. CMake – nie zajmuje się budowaniem projektu a raczej tworzy on konfigurację dla danego systemu – na Windowsie wygeneruje .vcproj dla dowolnej wersji Visuala, na Linuksie dostaniemy Makefile czy Ninja. Używa go wiele projektów co wydawało się dobrym wyborem. Chciałem zacząć go używać, przeglądnąłem dokumentację, oglądnąłem prezentację na jego temat i odrzuciło mnie – nie chcę uczyć się następnego języka aby tylko zbudować projekt. Do konfiguracji używany jest niestandardowy język oparty na makrach. Niepotrafiłem go skonfigurować żeby budował projekt tak jak tego oczekiwałem, a bardziej skomplikowana konfiguracja wymaga masy kodu – przykład. Szukałem dalej.

5. GYP – ten zapowiadał się ciekawie. Został stworzony przez Google i jest używany w takich projektach jak Chromium. Napisany w Pythonie, działa na tej zasadzie co CMake, ale używa JSONa. Nie chciałem używać Pythona (kolejna zależność) i mam wrażenie, że JSON nie nadaje się do pisania takich plików. Ciekawa opcja ale natknąłem się na coś innego

6. Premake – używa Lua, którego składnia przypomina YAMLa połączonego z JSONem. Prosty plik konfiguracyjny to kilka linii w pliku premake5.lua, a użycie ogranicza się do wpisania premake5 vs2013 i otrzymujemy plik projektu który zadziała z Visualem (zasada działania jak GYP i CMake). Z racji tego, że jest oparty o Lua który jest pełnoprawnym językiem skryptowym można napisać bardziej skomplikowane akcje gdy zajdzie taka potrzeba. Dokumentacja na wiki projektu jest krótka, ale opisuje to co ważniejsze – warianty buildów, dodatkowe flagi i inne. Wydaje się najlepszym rozwiązaniem na teraz. Przykładowy plik konfiguracyjny.

 

Pozostaje jeszcze kwestia zależności. Na Linuksie możemy poprosić użytkownika aby przed zbudowaniem projektu wykonał serię komend apt-get, które dociągną wymagane zależności. Na Windowsie to odpada, a poza tym nie wszystkie zależności są dostępne w ten sposób. Jedynym sensownym rozwiązaniem jest ściągnięcie bibliotek w formie kodu i dołączenie ich do projektu. Z pomocą przychodzi Git ze swoim Submodules – w pliku .gitmodules definiuje się zależności i adresy do repozytoriów. W ten sposób po zklonowaniu projektu wystarczy wydać polecenie git submodule update –-init i wszystkie zależności zostaną dociągnięte.

Do tej pory używałem projektu wygenerowanego przez Visual Studio, a gdy potrzebowałem skompilować program na Linuksie (Travis CI) używałem prostego skryptu shellowego. Zamierzam przenieść się na Premake i dopiero zobaczę czy to dobre rozwiązanie.

Jestem ciekaw jakie macie podejście do problemu poruszonego w tym wpisie – zapraszam do komentowania.

Opublikowano C++ | Skomentuj

Język i biblioteki

11123530043_1d28f2fa35_o

W tym krótkim poście opiszę jakich technologii korzystam przy Avocado i co zmotywowało mnie do ich użycia.

Język

Zdecydowałem się na użycie C++ z kilku powodów:

  1. Jest to język z którym mam najwięcej doświadczenia i wydaje mi się że pozwoli mi to na najszybszą zmianę pomysłu w działający kod. Myślałem nad wyborem jednej z nowszych alternatyw, m. in. Go albo Rust, ale obawiałem się że więcej czasu spędziłbym nad szukaniem rozwiązań do problemów, które w C++ są dla mnie oczywiste i spotkałem się z nimi przy poprzednich projektach (AnotherNES) niż nad pisaniem właściwego kodu.
  2. Duża ilość bibliotek - nie muszę martwić się o to, że biblioteka do obsługi danej funkcjonalności nie jest przeportowana lub zawiera błędy uniemożliwiające mi pracę nad projektem. W przypadku gdy nie będę chciał wynajdować koła od nowa mogę sięgnąć po gotowe rouzwiązanie.
  3. Wieloplatformowość - ten sam kod po niewielkich zmianach mogę skompilować na Linuksie, OS X, Androidzie, Raspberry Pi czy nawet do JavaScriptu (co pokazałem na moim poprzednim projekcie). C/C++ to chyba jedyne języki które mogę uruchomić na dowolnej architekturze nie tracąc na wydajności.
  4. Natywność - emulacja to taka dziedzina gdzie niskopoziomowość przy zarządzaniu pamięcią i procesorem jest koniecznością, a mechanizmy takie jak GC czy brak dostępu do surowej pamięci działa wyłącznie na naszą niekorzyść - dlatego odrzuciłem języki interpretowane i kompilowane w locie. Zostaje więc jedynie C, C++ i nowy Rust.
  5. Narzędzia - Visual Studio to jedno z najlepszych środowisk jakie miałem przyjemność używać (do póki nie poznałem środowisk firmy JetBrains). Szczególnie dopracowanym jest narzędziem jest tutaj debugger, z którym spędzam dużo czasu i bardzo usprawnia pracę. Poza światem Windowsa mamy LLVM + szereg narzędzi służących do analizy statycznej kodu, automatycznego formatowania składni czy badania programu pod kątem wycieków pamięci i innych popularnych błędów.

Biblioteki

SDL2 - używałem jej wiele razy, znam jej wady i zalety. Wersja 2.0 przyniosła wiele usprawnień jak obsługa wielu okien, sprzętowe renderowanie obrazu, unifikacja kontrolerów do gier (zmapowanie dowolnego kontrolera na schemat padu Xbox 360 który stał się swego rodzaju standardem na PC) i bezproblemowa obsługa OpenGL w nowszych wersjach. Najważniejszym argumentem który spowodował to że wybrałem ją zamiast SFML lub innej biblioteki jest (podobnie jak w przypadku C++) wieloplatformowość.

OpenGL 3.2 - specyfikacja w tej wersji zrywa kompatybilność wsteczną z tzw. fixed pipeline na rzecz shaderów. W poprzednich wersjach takie rzeczy jak światło, cienie czy tekstury były ustawiane z kodu, ale obecne karty graficzne (a konkretnie ich sterowniki) symulują te funkcjonalności w sprytnie zaszytych shaderach. Shadery dają ogromne możliwości przy tworzeniu grafiki komputerowej co może się przydać gdy będę chciał usprawniać grafikę generowaną przez konsolę. Dodatkowo wersja 3.2 jest bardzo zbliżona do OpenGl ES 2.0, który dominuje na urządzeniach niebędącymi komputerami osobistymi co znowu usprawnia proces portowania na inne platformy. Z Direct3D zrezygnowałem z powodu ograniczenia do jednej platformy.

Ciekawą technologią jest Vulkan, ale on dopiero raczkuje i jego celem nie jest zastąpienie OpenGl, a wypełnienie luki tam gdzie inne biblioteki powodują zbyt duży narzut wydajnościowy.

ImGUI - prosta biblioteka do tworzenia GUI posiadająca spore możliwości. Bardzo łatwo integruje się z bibliotekami 3d, jest szybka i banalna w użyciu. Jej implementacja zajmuje się całym boilerplate, my tylko definiujemy w locie jakie widgety chcemy wyświetlić i jak mamy zareagować na eventy. Pozwala na szybkie prototypowanie i tworzenie bardziej skomplikowanych narzędzi jak edytor pamięci, debugger i inne. W związku z tym, że generuje obraz w formie przyjaznej bibliotekom 3d nie ogranicza ją dana platforma.

Przy zgłaszaniu projektu na Daj się poznać wspomniałem też o HTML, CSS i JS. Potraktowałem to jako eksperyment, gdzie chciałbym powyższe technologie użyć jako zaawansowany interfejs do debugowania i manipulacji emulatorem. Serwerem byłby sam emulator który udostępniałby dane w czasie rzeczywistym z użyciem WebSocketów. Możliwości są duże - przykładowo wizualizacja aktualnie przesyłanych danych do GPU jak tekstury, modele i renderowanie ich z użyciem three.js.

 

Zapraszam do śledzenia na Twitterze oraz do komentowania.

Opublikowano Avocado | Otagowano , | 3 komentarze

Daj się poznać

Daj się poznać - logo

Już od dłuższego czasu po głowie chodziła mi myśl stworzenia własnego zakątka w sieci - strony gdzie mógłbym dzielić się moimi projektami które rozwijam w wolnym czasie, pisać o problemach z którymi się spotkałem i jak je rozwiązałem. Takie narzędzia jak Twitter czy Mikroblog są fajnymi miejscami łączącymi programistów, ale nie są stworzone do publikowania dłuższych treści (natomiast doskonale nadają się do podlinkowania swojego wpisu na blogu).

Przed założeniem tej strony zawsze powstrzymywały mnie różne rzeczy - a to nie mam serwera i domeny, a to nie wiem jaki silnik użyć czy też po prostu nie miałem czasu aby usiąść i zabrać się za to wszystko.

Początek

Zainspirował mnie felieton Macieja Aniserowicza o prowadzeniu bloga programistycznego, a impulsem do działania był wpis informujący o rozpoczęciu konkursu Daj się poznać. Tak się składa, że natknąłem się na blog Maćka 6 lat temu gdy organizował pierwszą edycję tego konkursu. Mimo iż wtedy tematyka bloga zbytnio nie interesowała mnie (.net) to blog zagościł w czytniku RSS i do dzisiaj jestem jego czytelnikiem.

Pamiętam, że w pierwszej edycji konkursu była dosłownie garstka uczestników i bardzo spodobała mi się seria o osdev Łukasza Sowy, gdyż interesowałem wtedy tym tematem, a na polskiej scenie takich artykułów było mało. Sam miałem pomysł na zgłoszenie swojego projektu, ale niestety na samym pomyśle się skończyło.

Projekt na konkurs

W tej edycji na konkurs zgłosiłem projekt który zacząłem pisać kilka miesięcy wcześniej w ramach oderwania się od pracy (inna technologia i całkowicie inna dziedzina).

Projekt nazywa się Avocado i jest to emulator konsoli Playstation.

To nie jest moja pierwsza styczność z emulacją - moim poprzednim dużym projektem był AnotherNES, czyli emulator konsoli znanej w Polsce pod nazwą Pegasus. Projekt rozwijałem na przestrzeni kilku lat z bardzo dużymi przerwami (zacząłem jeszcze w liceum, skończyłem na drugim roku studiów, gdzie posłużył jako zaliczenie programowania w C++). Było wszystko co lubię - stary sprzęt, assembler, trochę inżynierii wstecznej, programowanie niskopoziomowe i rzeczy które lubię mniej ale okazały się fajnym wyzwaniem: optymalizacja, aby już ponad 30 letnia 8-bitowa konsola z procesorem niewiele ponad 1.5MHz działała płynnie na laptopie sprzed dwóch lat, czy też kompilacja programu na platformy o których nie miałem wcześniej pojęcia że są w stanie podołać takiemu wyzwaniu jak emulacja - mowa tu o JavaScripcie za sprawą Emscripten.

AnothesNES

AnothesNES w pełnej okazałości

Tak, program napisany w C++ można skompilować do JavaScriptu i działa to zaskakująco dobrze. Na tyle dobrze, że po okrojeniu programu z zależności systemowych i bez żadnych optymalizacji emulator w przeglądarce działa w 60 fpsach, a da się też uruchomić przykładowo na telefonie.

LINK

NES to prosta 8-bitowa konsola - napisanie emulatora to nie jest aż tak trudny temat jak mogłoby się wydawać. Pomaga tutaj bardzo obszerna dokumentacja (na Visual6502 możemy zasymulować procesor i widzieć stany pojedynczych tranzystorów), jak i mnogość emulatorów tej konsoli - zawsze można zerknąć do źródła i podpatrzeć jakieś rozwiązanie. Napisać więc emulator to sprawa prosta. Napisać dobry emulator to trudne zajęcie - istnieją tysiące różnych gier na tę platformę i perfekcyjna emulacja każdej z nich jest sporym wyzwaniem.

Z Playstation jest inaczej. Emulatorów wspieranych do dzisiaj jest kilka, większość z nich oparta o stare i nieaktualne pluginy używające przestarzałych technologii. Chciałbym napisać coś co będzie w stanie konkurować z istniejącymi rozwiązaniami, nauczyć się nowych rzeczy i mieć projekt którym można się pochwalić.

Na koniec

Uff, rozpisałem się. Jak na razie to tyle - w kolejnych postach będę omawiał szczeguły projektu, zagadnienia związane z emulacją, z konsolą oraz sam postęp projektu i ewentualne problemy. Mam nadzieję że jesteś zaciekawiony tematem i będziesz śledził kolejne wpisy. Na zachętę dodaję zrzut ekranu z obecnej wersji:

Avocado

Avocado, ekran startowy konsoli

Opublikowano Blog | Otagowano , | Skomentuj