kategoria: ANSI C
[#1] [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób
Kręcę się wokół alokacji pamięci w grze i szukam najlepszego sposobu ogarnięcia tego tematu. Od strony struktur danych, funkcji. Założenia, które sobie obrałem są trochę karkołomne.
1. Hermetyzacja, na tyle na ile jest możliwa
2. Łatwość rozszerzania - chodzi o to by w jednym, dwóch miejscach coś dodać i mam załatwioną alokację kolejnego kawałka pamięci.
3. Pamięć typu wymagana (mandatory) - bez niej gra nie ruszy ( na przykład pamięć na bitplany )
4. Pamięć typu opcjonalna - użytkownik ma tyle pamięci, że można assety załadować za pierwszym razem a potem to już tylko kopiowania.

Doszedłem do dwóch rozwiązań i nie wiem które lepsze.
1. Każdy moduł ma funkcję init i kill - i tam odpowiednio przydzielam i zwalniam pamięć. Dodatkowo tam wystawiam adresy jako funkcję typu: ULONG TilesGetData(void).
2. Moduł Memory odpowiada za wszystko co jest związane z pamięcią. przydział i zwalnianie i udostępnianie.

Jakieś rady ? Chyba, że ktoś ma jeszcze jakiś inny pomysł to nadstawiam ucha.
[#2] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #1

Ja bym nie zabijał całego systemu i użył alokatora systemowego. Może nie jest jakiś nadzwyczajny, ale zaoszczędzi Ci kupę pisania (i debugowania...). Jeżeli gra jest NDOS i nie wraca do systemu, to możesz najpierw posprawdzać systemowym ile i jakiej masz pamięci, poalokować na wszystko, potem zabić sysa i olać zwalnianie.
[#3] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Krashan, post #2

Ja osobiście alokuję zasoby statycznie, na ile to możliwe, tzn. alokuję pamięć na całą strukturę, nawet jeśli są to elementy listy, czyli struktury dynamicznej. Trzeba uważać, by rezerwować tyle, ile rzeczywiście jest konieczne.

W przypadku gier najbardziej preferowany jest właśnie ten sposób (na pamięć wymaganą).

W przypadku gdy jednak wymagana jest alokacja dynamiczna, alokuję zasoby i bezpośrednio zwalniam po użyciu. Dotyczy to np. funkcji typu AllocIFF(), alokacji tymczasowych buforów, np. na spakowane dane itp.

Najlepiej jednak, by gra nie alokowała dynamicznie w czasie rozgrywki, tylko np. bezpośrednio przed. (Jest to preferowana metoda w przypadku pamięci opcjonalnej).

Nie wyodrębniam sobie modułu do zarządzania pamięcią. Bardziej stawiam na podany sposób nr 1 - czyli każdy moduł odpowiada za alokację niezbędnych zasobów.

Co do rozszerzalności, to można ją załatwić przez alokację np. listy węzłów z potrzebną pamięcią. Listę, jak wiadomo - łatwo rozszerzać.
[#4] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Krashan, post #2

Dzięki za odpowiedź. No tak pacan ze mnie, zapomniałem napisać że używam AllocMem i gra startuje z CLI/WB, os ubijam w następujący sposób
void OsStore(void)
{
	Forbid();
	oldView = GfxBase->ActiView;
	OsOwnBlitter();
	OsViewLoad(0);
	IoFlush();
	custom = ((struct Custom*)0xdff000);
	OsStoreHardwareRegs();
	HwStopDmaAndInts();
}


Tylko teraz nie wiem co to znaczy "nie zabijał całego systemu", możesz troszkę więcej szczegółów. Bo mi się wydaje że AllocMem i tak będzie działał jak ubije OS, tak sobie to pewnie źle wykombinowałem, bo co taki AllocMem potrzebuje do działania, chyba tylko pamięć :)
Przy inicjalizacji gry (zanim ubije OS) moge zaalokować pamięć i tak pewnie uczynię.
[#5] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #4

Bo mi się wydaje że AllocMem i tak będzie działał jak ubije OS
Też mi się tak wydaje, ale nie miałem okazji sprawdzić.
[#6] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #1

Ja tam zawsze staralem sie minimalizowac alokacje i zwalnianie pamieci, bo to kaszani pamiec, i od razu wiadomo, czy jest dosc pamieci, zeby program zadzialal. A nie po ilus minutach, nagle okaze sie, ze nie ma wolnej pamieci chip lub fast. Dlatego u mnie alokacje byly tylko max 2 razy wykonywane, raz dla czipu i raz dla fastu na poczatku. Na pamiec typu mandatory uzywalem hunkow BSS i BSS_C. Potem wyliczalem ile potrzeba fastu/chipu na opcjonalne dane. I alokowalem 1 duzy blok fastu i 1 czipu.
Ale to ma byc w C, wiec sorry, to nie dotyczy, choc moze da sie tez zrobic.

Ostatnia aktualizacja: 09.07.2020 01:57:04 przez Don_Adan
[#7] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #4

Allocmem potrzebuje do dzialania exec.library oraz cala infrastrukture systemowa - execbase itp. W zaleznosci jak zdefiniujesz "ubicie OS", to albo bedzie dzialac albo nie :)
Zazwyczaj "ubicie OS" to przejecie pelnej kontroli nad maszyna - przerwaniami, customchipami, cala pamiecia (wraz z obszarami systemowymi) itp. W takim przypadku AllocMem nie bedzie dzialal. Jesli bedziesz wykorzystywal tylko obszary pamieci, ktora zaalokowales przez system nie uszkadzajac obszarow systemowych, to AllocMem bedzie dzialal.
Jesli Twoja gra nie potrzebuje calej dostepnej pamieci ani kazdego cyklu cpu albo np bedzie ladowac pliki z dysku, to sugerowalbym rozwiazanie bardziej systemowo przyjazne:
- nie manipulowac bezposrednio przerwaniami tylko np. uzyc AddIntServer,
- korzystac bezposrednio z blittera tylko po OwnBlitter, a najlepiej poprzez funkcje graphics.library,
- wykorzystac lowlevel.library np. do obslugi joysticka
- jesli potrzebna jest cala moc cpu, to na czas gry mozna podniesc priorytet tasku do 127, co praktycznie wylaczy przelaczanie zadan bez zabijania systemu itp.
- uzywaj memory pools do alokacji pamieci

Ostatnia aktualizacja: 09.07.2020 03:42:56 przez docent
[#8] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #1

1. Każdy moduł ma funkcję init i kill - i tam odpowiednio przydzielam i zwalniam pamięć. Dodatkowo tam wystawiam adresy jako funkcję typu: ULONG TilesGetData(void).
2. Moduł Memory odpowiada za wszystko co jest związane z pamięcią. przydział i zwalnianie i udostępnianie.


Ja bym nie zabijał całego systemu i użył alokatora systemowego


Brzmi bardzo mocno jak ACE. ;) Z tym że ACE umie na chwilę "od-ubić" niezbędny kawałek systemu jak nagle potrzebujesz coś doalokować.

Statyczna alokacja "wszystkiego" czy inny memory pool ma ten minus, że potem siedzą Ci wszystkie zmienne w pamięci - te od menu, te od samej gry, od game overa, od creditsów. W skrzętnie prowadzonej alokacji dynamicznej nie ma tego problemu. Statycznej używam tylko do globali, które są na całą grę (np. definicje stanów w maszynie stanu gry). Reszta to dynamiczne alokacje stosowane w modułach, które wystawione mają create/destroy/process. Całkiem nieźle to działa.

Wracając do 4 punktów z pierwszego posta:

1. to Ci załatwia podział na moduły, każdy moduł ma swoją strukturę
2. alokujesz/zwalniasz zawsze sizeofem tej struktury, a nawet lepiej sizeofem zmiennej: tManager *pManager = memAllocFast(sizeof(*pManager));
3. Zrób sobie logging alokacji jak budujesz w trybie Debug, i najlepiej na koniec podsumowanie ile szczytowo pamięci zostało zaalokowane (znowu ACE ;) ). Na koniec tworzenia gry zrób test czy availmem zwraca odpowiednio dużo, najlepiej z marginesem 10%-20% na pofragmentowaną pamięć. Nie jest to idealne, ale przeważnie robi robotę.

W sumie tak sobie myślę, że można by memory manager w trybie debug uzbroić w analizowanie używanej pamięci i dodawać mu flagę optional/mandatory - na koniec by wypluwał ile było takiej i takiej i najlepiej jeszcze w jakich kawałkach. Potem mu tę informację podać na wejście, zbudować na release i albo będzie w stanie zaalokować na dzień dobry pamięć o takich a takich kawałkach (zaczynając od największego) albo wysypka. A potem jak przez niego byś alokował, to przydzielałby te kawałki, w formie 1:1 co do rozmiaru - ten co chce 5 bajtów nie dostanie kawałka z 20 bajtami itd.

Na marginesie:

Na pamiec typu mandatory uzywalem hunkow BSS i BSS_C. Potem wyliczalem ile potrzeba fastu/chipu na opcjonalne dane. I alokowalem 1 duzy blok fastu i 1 czipu.
Ale to ma byc w C, wiec sorry, to nie dotyczy, choc moze da sie tez zrobic.


Można zmiennym nadawać atrybut sekcji w jakich mają siedzieć. Jeśli GCC nowożytne (czy Bartman czy Bebbo) nie obsługują tego konkretnego typu hunka, to jest to bug, raczej łatwo przez nich naprawialny.

A co do lowlevel.library - to tylko OS3.0+. A myślę że Asman z przyzwyczajenia ciśnie ks1.3.

Ostatnia aktualizacja: 09.07.2020 08:59:52 przez teh_KaiN
[#9] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@teh_KaiN, post #8

Dzięki wszystkim za odpowiedzi. Raczej będę szedł w kierunku tego co napisał Don, czyli zrobię maks trzy alokacje, i dopisze prosty manager (o ile to można tak nazwać) pamięci typu GetChip, GetAny. Nad opcjonalną pamięcią się jeszcze zastanowię, gdyż być może nie będzie ona potrzebna, bo na przykład popakuje wszystko.

@teh_KaiN
Brzmi bardzo mocno jak ACE. ;)

ACE dla mnie jest deczko za ciężkie.
Z tym że ACE umie na chwilę "od-ubić" niezbędny kawałek systemu jak nagle potrzebujesz coś doalokować.

A co jak doalokacje się nie powiedzie ? Rozumiem że programista musi to ogarnąć jakoś wtedy.

Ja jakoś chyba opacznie rozumiem statyczną alokację, bo dla mnie to jest obszar pamięci tworzony na stosie. A to może powodować bardzo śmieszne błędy gdy nagle przekroczymy granicę.

Co do logów to jeszcze przede mną, wolałbym raczej testy - i teraz siedzę i myślę nad tym :)

A co do lowlevel.library - to tylko OS3.0+. A myślę że Asman z przyzwyczajenia ciśnie ks1.3.


Tak to k1.3+ i wymagania A500 0.5 MB CHIP. To jest stary projekt, który muszę dojechać.

Edit: PS. Jak mnie mocno zdenerwuje generowany kod z C, to przeskoczę na asm ;)

Ostatnia aktualizacja: 10.07.2020 22:31:55 przez asman
[#10] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #9

Ja jakoś chyba opacznie rozumiem statyczną alokację, bo dla mnie to jest obszar pamięci tworzony na stosie. A to może powodować bardzo śmieszne błędy gdy nagle przekroczymy granicę.

Ja miałem na myśli statyczną alokację jako alokację zasobów na stercie "na zapas", tak żeby uniknąć wielokrotnej alokacji i zwalniania. Przydatne, gdy np. wiemy że w grze jest max. 10 obiektów i alokujemy tyle przestrzeni ile potrzeba, tak żeby nie wypisywać graczowi informacji o braku pamięci podczas rozgrywki (bo np. gracz zechciałby zapisać stan gry).

Najlepsza jest oczywiście alokacja dynamiczna węzłów listy. Ale czasami np. jak handler input.device, chcemy by nie alokował pamięci 100 razy co chwila, wtedy robimy taki bufor.

Jeżeli chodzi o stos i zmienne lokalne, to zupełnie inna para kaloszy. Przepraszam, ale może słowo "statyczna alokacja" wprowadziła w błąd, miałem na myśli alokację pamięci na stercie z wyprzedzeniem, a nie dane na stosie.

Ostatnia aktualizacja: 10.07.2020 22:55:08 przez Hexmage960
[#11] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #9

Dwie alokacje wystarcza wedlug mnie jedna dla czipu, druga dla fastu. Mozesz probowac alokowac najpierw maksimum fastu (czyli potrzebna i opcjonalna pamiec), jak alokacja wypali to ok, jak nie to zmniejszasz do minimum, no chyba ze jeszcze jakies posrednie stopnie alokacji, ale na to zwykle szkoda czasu. Tak samo z alokacja czip ramu, od maksimum do minimum. No i jak to zrobisz przed wylaczeniem systemu to mozesz jeszcze dane wczytac bez kombinowania.
[#12] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #9

Dynamiczna alokacja jak nazwa wskazuje jest zmienna w czasie. Statyczna nie. Statyczna to regiony .bss i .data (wszystkie globale) alokowane przez system na początku uruchomienia programu. Stos taką nie jest, bo raz jest na nim więcej a raz mniej, zwłaszcza jak pojawi się rekursja. Chyba że patrzysz na jego całość, to tak, dostajesz domyślnie te 4k/10k ramu i cześć. ;)

ACE dla mnie jest deczko za ciężkie.


To po to ja się starałem dzielić ACE'a na moduły, co by ktoś mógł sobie odpalić tylko jego fragmenty, żeby usłyszeć że jest za ciężki. Ranisz mnie. ;)

A co jak doalokacje się nie powiedzie ? Rozumiem że programista musi to ogarnąć jakoś wtedy.


No przecież, ktoś musi sprawdzić czy alokacja zwróciła null czy nie. Możesz zrobić w swoim podejściu tak, że jak moduł się inicjuje (funkcja create()) i części nie uda mu się zaalokować, to mu to ustawia boola który jest potem sprawdzany w funkcji process() i w zależności od tego moduł coś robi inaczej, albo w ogóle nic. Tylko wtedy wypadałoby zrobić to tak, żeby na samym początku szedł minimalny create() wszystkiego. Jak nie starczy RAMu to strzelasz komunikat i wychodzisz. Potem robisz createExtra() i tu co się zmieści to fajnie, a co nie to pracuje w wersji minimalnej.

Ostatnia aktualizacja: 11.07.2020 09:26:21 przez teh_KaiN
[#13] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Don_Adan, post #11

A co z przypadkiem, gdy użytkownik ma tylko CHIP. Dla gry najważniejsza pamięć (w kolejności)
1. CHIP
2. ANY (inna może być i CHIP gdy użytkownik nie ma fast).

To według mnie wymusza kolejność alokacji. Najpierw CHIP a potem już ANY. Chciałbym by to było maksymalnie proste.
Co do wczytywania przed ubiciem os, to na pewno uprości i chcę tak zrobić.

Za tą pamięcią to najwięcej zajmują assety związane z muzyką. Niespakowane to 328884 bajtów (9 modułów) i sfx 135 468.
Narazie spakowałem Shrinklerem moduły to wyszło 62864 bajty. Sfx nie pakowałem jeszcze, bo będę obniżał z 22050 na 11025 a potem je spakuje. Tylko jeszcze nie ogarniałem jak to zrobić, gdyż sample są w formacie raw. Znasz może jakieś narzędzie, które zrobi z 22050 na 11025 ?

W każdym razie będę łączył C z asm bo depacker shrinklera jest w asm.
[#14] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@teh_KaiN, post #12

A propos ACE, fajnie by było gdybyś napisał artka na PPA jak to ugryźć z bardzo prostymi przykładami i pokazać takiemu niedowiarkowi jak ja, że faktycznie jest lekki. W sumie można zrobić małe wyzwanie - typu jak napiszesz w ciągu tygodnia/miesiąca to ja w tym czasie ogarnę i napiszę 3 odcinek pisania slave'a pod WHDLoad i 3 odcinek asm. To byłaby duża motywacja dla mnie. Tylko daj znak i ramy czasowe i wio.

Ostatnia aktualizacja: 11.07.2020 10:46:05 przez asman
[#15] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #13

Znasz może jakieś narzędzie, które zrobi z 22050 na 11025?
Na Amidze SoX. Poza tym Audacity.
[#16] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@asman, post #13

Dawno nie alokowalem pamieci, ale dla chipu to chyba bylo moveq #2,d1 a dla dowolnej to bylo moveq #1,d1. Fastu jako fastu moveq #4,d1 nigdy nie alokowalem, bo wtedy bez fastu sie nie da. Wiec to pamiec dowolna bylaby. Zawsze taka alokuje, w domysle jako fast, choc to moze byc tez chip, jak ktos nie ma fastu. Kolejnosc alokacji to bym chyba najpierw dowolna (any) alokowal, bo ona jest zwykle zawsze jako pierwsza uzywana. Ale to moze tez zalezec ile jakiej pamieci potrzebujesz.
Co do sampli to zwykle sam takie przerabialem, prosta procedura 2 w 1:
move.b (a0)+,d0 ; source
ext.w D0
move.b (a0)+,d1
ext.w d1
add.w d1,d0
lsr.w #1,d0
move.b D0, (a1)+ ; destination
skonwertuj i posluchaj czy dobrze brzmi.
[#17] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Don_Adan, post #16

Dla dowolnej pamięci można podać zero. 1 to jest MEMF_PUBLIC, co w praktyce niczym się nie różni, przynajmniej na klasycznym AmigaOS w typowej konfiguracji sprzętu. W zamyśle MEMF_PUBLIC gwarantuje, że do pamięci mogą mieć dostęp wszystkie procesy, więc dla zasady z tą flagą alokuje się np. wiadomości wysyłane do MsgPortów, IORequesty i tym podobne. Z tym, że według mojej wiedzy, w systemach 3.x to i tak sztuka dla sztuki.

Resamplowanie przez liczenie średniej dwóch sąsiednich próbek jest całkiem niezłe, chociaż otrzymany w ten sposób „filtr” nie ma optymalnej charakterystyki. Resamplując filtrem optymalnym można jeszcze trochę ugrać na jakości sampla. Z tym, że to oczywiście nie w czasie wykonania programu, bo taki optymalny filtr ma większą złożoność obliczeniową.
[#18] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Krashan, post #17

Kiedys potrzebowalem skrocic w prosty sposob sample, bez uzywania mnozenia i dzielenia. I tak mi wyszlo, ze taki sposob daje calkiem dobra jakosc, przerobilem jakis dluzszy sampel i go przesluchalem, roznicy nie uslyszalem, ale nie mam super sluchu. Byc moze lepsza bylaby taka wersja:
sub.w d0,d0 ; clear X
loop
move.b (a0)+,d0 ; source
ext.w D0
move.b (a0)+,d1
ext.w d1
addx.w d1,d0
lsr.w #1,d0
move.b D0, (a1)+ ; destination
dbf D2, loop
[#19] Re: [C, Gamedev] Alokacja pamięci dla gry - najlepszy sposób

@Don_Adan, post #18

Dzięki Krashan, Don. Teraz mi się przypomniało że pod Windows używałem SoX.
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem