MorphOS.pl – Polska strona użytkowników MorphOS-a
MorphOS.pl – Polska strona użytkowników MorphOS-a

Strona główna Forums Dla programistów Pętla główna GL-aplikacji

Znaczniki: 

Widok 7 wpisów - 1 z 7 (of 7 wszystkich)
  • Autor
    Wpisy
  • #5658
    MDWMDW
    Participant

    W tym artykule Krashan ładnie pokazuje jak stworzyć przyjazną systemowi pętlę główną i narysować coś przy pomocy TinyGL. Jednak przykład ten rysuje trójkąty i potem czeka na obsługę zdarzeń na jakie jest “zapisany”. A co jeżeli chciałbym aby funkcja rysująca była wywołana tyle razy na sekundę ile razy jest odświeżany ekran? I po każdym wywołaniu żeby w sposób przyjazny dla reszty systemu pętla czekała na kolejne odświeżenie, odbierając od systemu zdarzenia. Jak się to powinno wzorcowo zrobić? Jest jakieś takie zdarzenie od systemu, które mogę obsłużyć i wywołać wtedy swoją funkcję? Gdzie mam szukać przykładu?

    Teraz jeszcze wytłumaczę co ja właściwie próbuję osiągnąć. Część zupełnie zbędna i tylko dla mocno zainteresowanych (albo nudzących się). 🙂

    Próbuję pozbyć się całkowicie SDL ze swoich wypocin. Biblioteka SDL zwalnia z konieczności poznawania systemowego API na każdej platformie. Jednak z czasem zauważyłem, że rozwiązywanie problemów, tworzenie obejść i wypracowywanie kompromisów związanym z SDL zjada mi więcej czasu niż gdybym napisał podstawową część “silnika” natywnie w każdym obsługiwanym systemie operacyjnym. Tym bardziej, że da mi to większe możliwości i dostęp do specyficznych funkcji danego systemu operacyjnego.
    To co tam sobie piszę w wersji dla iOS nigdy nie korzystało z SDL. Wersję dla macOS właśnie po 3 miesiącach dłubania skończyłem przepisywać, bo radzę sobie pisaniem pod applowe systemy. Przyszła kolej na to żeby pozbyć się SDL z wersji dla MorphOS.

    Wszystko mam tak zorganizowane, że 99% kodu jest multiplatformowa i całkowicie odseparowana od kodu specyficznego dla danej platformy, kompiluje się bez jakichkolwiek zmian praktycznie na każdej platformie na której da się pisać w prostym C++ i prehistorycznym OpenGL. Jednak cały ten mechanizm potrzebuje pewnych informacji, danych i zdarzeń pochodzących od systemu operacyjnego, które muszę “przepchnąć” dalej do części multiplatformowej. Do tej pory tym “systemem operacyjnym” był dla mnie SDL (na każdej platformie oprócz iOS). Teraz sam muszę sobie napisać ten niewielki kawałek SDL z którego do tej pory korzystałem. Czyli potrzebuję:
    – pętlę główną, która wywoła mi zadaną funkcję tyle razy na sekundę jakie jest odświeżanie ekranu i po każdym wywołaniu poczeka (w sposób przyjazny systemowi) do końca “ramki”,
    – zdarzenia od myszy, klawiatury, okna, menu,
    – zdarzenie uruchomienia aplikacji,
    – zdarzenie zamknięcia aplikacji,
    – zdarzenie aktywowania/dezaktywowania okna,
    – zdarzenie przeskalowania okna,
    – zdarzenie ukrywania/pokazywania (ikonifikacji/deikonifikacji) okna,
    – otwieranie okna/ekranu w zadanych parametrach (rozdzielczość, wielkość, gadżety),
    – listę dostępnych trybów graficznych,
    – chowanie/pokazywanie i zmiana wyglądu wskaźnika myszy,
    – aktualny “tick” czasu (do obliczenia ile czasu minęło od ostatniego wykonania pętli głównej).

    SDL używałem też od odtwarzania dźwięków więc będę musiał też systemowymi sposobami załatwić:
    – równoczesne odtwarzanie wielu krótkich dźwięków zapisanych w pamięci,
    – odtwarzanie muzyki (streaming z dysku albo z pamięci).

    I to tyle. Pozostały kod oparty obecnie na (całkiem niemałych) 264 plikach C/C++ będzie działał poprawnie jeżeli dostarczę mu to wszystko co wymieniłem. No i raz napisane podstawy będę działały zawsze i nie będę musiał do nich zaglądać jeżeli nie będę chciał obsłużyć czegoś nowego co będzie wymagało sięgnięcia do systemu operacyjnego albo system operacyjny coś mocno zmieni (to raczej nie na amigowych systemach).

    #5659
    Avatarkrashan
    Participant

    Powinieneś podpiąć procedurę pod przerwanie odświeżania obrazu. Na klasyku dodawałeś (systemowo, a jakże) handler do „ringu” handlerów przerwania VERTB (odchylania pionowego obrazu). Można założyć, że pod MorphOS-em to przerwanie jest co najmniej symulowane i AddIntServer() zadziała zgodnie z oczekiwaniami. A może nawet ono jest faktycznie wyzwalane rzeczywistym przerwaniem VERTB z karty graficznej, chociaż osobiście wątpię… To jest pytanie do Franka Mariaka i Marka Olsena. Jeżeli jest symulowane to możesz nie mieć synchronizacji aktualizacji ekranu z zmianą ramki.

    W procedurze obsługi przerwania najprościej wysłać sobie sygnał do swojego procesu. W głównej pętli dodasz wtedy ten sygnał do zasadniczego Wait(). O ile procedura wywoływana po odebraniu tego sygnału zdąży się wykonać w jednej ramce, to będzie wywoływana co ramkę. A jak nie, to nic się nie stanie, wielokrotne ustawienie procesowi sygnału nie ma konsekwencji.

    #5660
    MDWMDW
    Participant

    Brzmi to bardzo dobrze (zwłaszcza drugi akapit) i wygląda na to, że właśnie o coś takiego mi chodzi. Niestety moja znajomość amigowego API jest bliska zeru i będę musiał poszukać jakiegoś przykładu. Pisanie pod amigowe systemy różni się od pisania na inne platformy przede wszystkim tym, że problemów nie rozwiązuje się przy pomocy Google czy StackOverflow. 😉 🙂 Dokumentacja też jest skromniejsza. Dlatego takie artykuły jak Twój są bezcennymi perełkami. 🙂

    #5662
    Avatarkrashan
    Participant

    Aha, jeżeli sobie odpuścimy kwestię synchronizacji rysowania sceny z odświeżaniem ramki, to zamiast zabawy z przerwaniami można po prostu użyć timer.device i mieć wywołanie co wybrany okres czasu. Zaletą wtedy jest to, że rytm wywoływania procedury nie zależy w żaden sposób od rzeczywistego odświeżania ekranu.

    Jak znajdę czas na urlopie, to spróbuję dopisać artykuł, w którym zakręcę tymi trójkątami…

    #5663
    MDWMDW
    Participant

    Właściwie nie zależy mi na tym żeby procedurka przeliczania i rysowania zawartości okna była dokładnie zsynchronizowana z odświeżaniem ramki. Ale chciałbym żeby była wywoływana tyle razy ile razy na sekudnę jest odświeżany ekran. Jeżeli da się tę częstotliwość jakoś pobrać z systemu to wystarczyłby mi zwykły timer ustawiony na taką właśnie częstotliwość. Nienawidzę rysowania czegoś np. 100 razy na sekundę podczas gdy obraz na monitorze zmienia się tylko 60 razy na sekundę. To jest taka trochę para w gwizdek. 🙂 A jak taki timer działa jeżeli procedura rysowania nie zmieści się w 1/60 sekundy (zakładając 60Hz)? Kolejne wywołanie procedury nie odbędzie się czy mam robić jakąś flagę, którą sprawdzę sobie czy poprzednie rysowanie się już zakończyło i kolejnego nie uruchamiać?

    Jak znajdę czas na urlopie, to spróbuję dopisać artykuł, w którym zakręcę tymi trójkątami…
    Znajdź czas! Znajdź czas! Znajdź czas! 🙂

    #5672
    Avatarkrashan
    Participant

    Co się będzie działo w przypadku „nie wyrabiamy się w ramce” zależy od organizacji procedury. Do timer.device wysyła się IORequest (wiadomość), która zostaje zwrócona albo po upływie określonego interwału czasowego, albo gdy czas systemowy osiągnie zadaną wartość. W najprostszym przypadku nasza procedura coramkowa wykorzystuje drugi tryb, za każdym razem po narysowaniu wszystkiego dodając te 1/60 sekundy do poprzedniej wartości i ponownie wysyłając request, na który czeka główna pętla. Co się stanie, gdy procedura będzie się wykonywać za długo? timer.device zwróci requesta od razu. Główna pętla nie będzie czekała, tylko będzie non-stop wywoływała naszą ramkową procedurę najszybciej jak się da.

    Takie rozwiązanie jest słabe z dwóch powodów. Po pierwsze, jeżeli animację obiektów oprzesz o zliczanie wywołań procedury, to na niewyrabiającym się kompie akcja gry zwolni, podczas gdy zazwyczaj w takiej sytuacji słabego sprzętu chcemy mieć mniej fps, ale rzeczy mają się nadal dziać w czasie rzeczywistym. Po drugie, jeżeli np. w grze chwilowo mamy więcej obiektów i nie wyrabiamy się, ale po chwili robi się luźniej, to nasz mechanizm będzie potem „nadganiał” i gra będzie się toczyła szybciej niż normalnie.

    Dlatego lepiej sobie na początku naszej procki odczytać czas rzeczywisty (też timer.device, odczyt jest bardzo tani w sensie czasu CPU) i po pierwsze wykorzystać go do animacji i fizyki, po drugie do zarządzania tempem renderingu (np. jeżeli jesteśmy w niedoczasie to można w ogóle nie wysyłać requesta do timer.device tylko dać na wyjściu flagę „wywołaj mnie ponownie bez czekania”).

    #5673
    MDWMDW
    Participant

    U mnie zawsze w każdej wersji na początku takiej pętli jest pobierany aktualny czas i od niego odejmowany jest czas ostatniej aktualizacji/rysowania. I taka różnica czasu jest podawana dalej do samego silnika. Przez tę różnicę jest przemnażany każdy zmienny w czasie parametr. Wobec tego nie ma absolutnie szans żeby coś się rozjechało na bardzo szybkich czy bardzo wolnych komputerach. Obiekt w zadanym czasie przebywa dokładnie tę samą prędkość niezależnie czy animacja chodzi 0,5 FPS czy 60 FPS.

    Taka władza nad czasem podawanym silnikowi ma kilka zalet. Na przykład gdy chcę zgrywać film z gry do klatek to nie podaję prawdziwej różnicy czasu tylko wywołuję update zawsze z czasem 1/60 sekundy i mam film w którym jest idealnie 60 klatek na sekundę.
    Druga zaleta, że pauza gry robi się właściwie sama. Wystarczy, że różnica czasu będzie 0. Wtedy wszystko się ładnie zatrzymuje i ani drgnie do momentu wyłączenia pauzy. 🙂

    Ja generalnie mam tylko problem z czysto amigowym API. Nie bardzo mogę znaleźć przykład jak zrobić taki timer, który mi wywoła jakąś procedurę co określony czas. Niby dokumentacja jest ale bardzo “sucha”. Trzeba mieć jednak trochę doświadczenia żeby z niej korzystać. 🙂

Widok 7 wpisów - 1 z 7 (of 7 wszystkich)
  • Musisz być zalogowany aby odpowiedzieć na ten temat.