SDL 320x240

0

Witam. Tworzę grę w C++ używając SDL. Chciałbym, aby tworzona przeze mnie gra miała rozdzielczość 320x240 (fullscreen), jednak gdy jej używam, na ekranie wyświetla się jedynie niewielki obszar na środku na czarnym tle. Wyczytałem na innym forum poradę, by używać rozdzielczości 640x480 (z nią nie mam żadnych problemów), po wcześniejszym przeskalowaniu powierzchni na której pracuję (320x240) na ten rozmiar.

I tu pojawia się pytanie: jak tego dokonać? Mam bufor wielkości 320x240 i chcę go wyświetlić powiększony na powierzchni ekran (640x480).
Gdy używam funkcji zoomSurface z SDL_gfx, aby powiększać bufor za każda klatką otrzymuję jedynie czarny ekran i natychmiastowe zajęcie całej dostępnej pamięci RAM (2GB). Czy istnieją jakieś inne sposoby (próbowałem również własnej funkcji do skalowania na podstawie jakiegoś tutoriala, ale FPS spadał katastroficznie i pojawiały się inne problemy)?
Z góry dziękuję za pomoc.

0

Użyłeś tego: http://www.sdltutorials.com/sdl-scale-surface ?

Cóż, zmiana wielkości surface w każdej klatce przy softwarowym rendererze będzie zawsze wolna...

Ale twój problem prawdopodobnie jest spowodowany tym - SDL_CreateRGBSurface - w dodatku prawdopodobnie jej nie zwalniasz przez co tracisz szybko RAM.
Możesz nie tworzyć nowej powierzchni za każdym razem tylko używać ciągle jednej, którą będziesz nadpisywał za każdym razem.
I jeszcze używanie DrawPixel etc dla każdego piksela jest pewnie wolne, lepiej to robić hurtowo na jakiejś tablicy.

0

SDL jest ogólnie bardzo wolny. Jak dasz SDL_BlitSurface w jakiejś pętli to potrafi spowolnić program o jakieś 1000FPS. Dlatego ja jak piszę w SDL to posługuje się trickiem ze zapisywaniem backupowej powierzchni ekranu a póżniej jak chcę przesunąć jakiś obiekt to przywracym mu powierzchnię zastępczą a później odrysowywuje go w innym miejscu oraz odświeżam tylko te miejsca które powinienem odświeżyc(tam gdzie obiekt był kiedyś i wtedy kiedy jest teraz). Załóżmy że bym miał w funckji redraw() która wykonuje się w pętli głównej dużo funckji SDL_BlitSurface to program by wyciągnał góra 2 FPS. Ogólnie to nie polecam SDL po aby osiągnąć na nim dobrą wydajność należy pokombinować dlatego radziłbym się nauczyć Open GL Tam masz funckję ViewPort która zmieni ci obszar renderingu i jak np rozciągniesz okno to wszystrkie elementy będą też odpowiednio przeskalowane.

0
robcio napisał(a):

SDL jest ogólnie bardzo wolny.

Wystarczająco szybki. Nie jest przyśpieszany sprzętowo jak OpenGL/DX, ale co z tego skoro tych możliwości się i tak na początku nie wykorzystuje a napisanie czegoś w DX / nowoczesnym OpenGL to na początku męczarnia...

Jak dasz SDL_BlitSurface w jakiejś pętli to potrafi spowolnić program o jakieś 1000FPS.

Nie ma czegoś takiego jak zwolnienie o 1000 FPS... FPS to nie jednostka czasu. Możesz powiedzieć że czas renderowania klatki zwiększa się o xx ms, mówienie o zwolnieniu o ileśtam FPS to absurd...

Załóżmy że bym miał w funckji redraw() która wykonuje się w pętli głównej dużo funckji SDL_BlitSurface to program by wyciągnał góra 2 FPS.

Dużo to niezbyt precyzyjne pojęcie. A te 2 FPS to na pięcioletnim laptopie czy czteroGHzowym intelu (tak, też nieścisłe)?

Ogólnie to nie polecam SDL po aby osiągnąć na nim dobrą wydajność należy pokombinować dlatego radziłbym się nauczyć Open GL Tam masz funckję ViewPort która zmieni ci obszar renderingu i jak np rozciągniesz okno to wszystrkie elementy będą też odpowiednio przeskalowane.

SDL jest prosty. Naprawdę, porządne pisanie w OpenGL jest bardzo trudne, nawet starodawne używanie fixed pipeline jest dość skomplikowane bo OGL jest biblioteką niskopoziomową (na przykład - porównaj trudność wczytania obrazu w OpenGL i SDL)... Niepotrzebnie straszysz ludzi IMO.

0
msm napisał(a):

Użyłeś tego: http://www.sdltutorials.com/sdl-scale-surface ?

Właśnie tego używałem (gdy zrezygnowałem z zoomSurface), z tragicznym efektem (20 FPS i też problemy z pamiecią - domyślam się, że coś pomieszałem)

Ale twój problem prawdopodobnie jest spowodowany tym - SDL_CreateRGBSurface - w dodatku prawdopodobnie jej nie zwalniasz przez co tracisz szybko RAM.
Możesz nie tworzyć nowej powierzchni za każdym razem tylko używać ciągle jednej, którą będziesz nadpisywał za każdym razem.

Hmm, SDL_CreateRGBSurface znajdowało się poza pętlą główną - a i tak występował problem...

Mój kod (wersja z sdl-scale-surface z polecanego powyżej linku - efekt - wyświetla się OK, ale 20 FPS i w ciągu kilku sekund pożera całą dostępną pamięć)

void App::Run() {
    // inicjalizacja okna
    SDL_Init(SDL_INIT_VIDEO);
   
    resx=320; resy=240;
    
    SDL_Surface * print= SDL_SetVideoMode( 640, 480, 32, SDL_FULLSCREEN | SDL_SWSURFACE );
    SDL_PixelFormat *format = print->format;
    ekran=SDL_CreateRGBSurface(SDL_SWSURFACE, resx, resy, format->BitsPerPixel, format->Rmask,  format->Gmask, format->Bmask,  format->Amask);
    
   
    SDL_Rect dest;
    dest.x=0;dest.y=0;dest.w=640;dest.h=480;
  
   //inicjowanie różnych tam pierdół(...)

    // pętla główna
    wyjscie = false;
    int czas=SDL_GetTicks();
   

    while (!wyjscie) 
        
        ProcessEvents();

        // time update
        int dt=SDL_GetTicks()-czas;
        czas=SDL_GetTicks();

        // update & render
        if (dt > 0) {
            Update(dt);

        }
        

//obliczanie FPS(...)
     
        
       SDL_BlitSurface( ScaleSurface(ekran,640,480),NULL,print, & dest);
//w drugiej wersji zamiast powyższej linijki jest:
//print = zoomSurface (ekran, 2, 2, 0); 
//efekt taki, że również pożera pamięć tyle, że wyświetla tylko czarny ekran
		
        SDL_Flip(print);

        Uint32 colorkey = SDL_MapRGB(ekran->format,70,70,70); 
        SDL_FillRect(ekran,NULL,colorkey);
        mapa.Draw(camx,camy);
        Draw();
   

    }
    //zwalnianie pamięci(...)
    SDL_Quit();
} 
0

Nie dość ,że skalujesz powierzchnię to jeszcze ją blitujesz na ekran co zżera ci większą ilość FPS. Na dodatek znajduje się to w głównej pętli. Ja bym proponował zrobić tak:

Daj

SDL_BlitSurface( ScaleSurface(ekran,640,480),NULL,print, & dest);

poza główną pętle(na sam początek).

usuń te trzy linijki

SDL_Flip(print);
Uint32 colorkey = SDL_MapRGB(ekran->format,70,70,70);
SDL_FillRect(ekran,NULL,colorkey);

Spodziewam się ,że chcesz przesuwać kamerą po mapie. Tutaj nie ma wyboru musisz narysować czworokąt znajdujący się w innym miejscu mapy. Najpierw zrób sobie funkcję:

SDL_Surface * backupDisplay(int x1, int y1, int w1, int h1)
{
	SDL_Surface * bb;
	bb = createSurface(0,0,0,w1,h1);

	SDL_Rect rct;
	rct.x = x1;
	rct.y = y1;
	rct.w = w1;
	rct.h = h1;

	applySurface(0,0,video,bb, &rct); //kopiujemy kawałek bufora ekranu do powierzchni zapasowej
	return bb;
}
 

Tworzy ona powierzchnię zapasową ekranu(będzie ona potrzebna do przywracania fragmentów ekranu). Załóżmy że będziesz się chciał poruszać po mapie strzałkami. Jeśli będziesz chciał przesunąć kamerę w inne miejsce to musisz tylko blitować właściwy kawałek powierzchni mapy na ekran dzięki czemu zostanie zatarty stary kawałek mapy(nie musisz już używać SDL_FillRect do czyszczenia ekranu). Później odśwież cały ekran. Zauważ że ekran będzie się cały odświeżał tylko jak będziesz poruszał się po mapie. Podam teraz przykład poruszania się postaci po mapie:
Jak wklejasz powierzchnię(np bitmapę) na bufor ekranu to najpierw musisz utworzyć powierzchnię zapasową dokładnie tego miejsca na ekranie gdzie będziesz wklejał ową bitmapę. Później jak będziesz chciał przesunąć postać w inne miejsce to wklejasz powierzchnię zapasową ekranu którą wcześniej zapamiętałeś i blitujesz postać w inne miejsce na ekranie(oczywiście uprzednio zapamiętując powierzchnię "backupową" ekranu). Następnie wystarczy odświeżyć funckją UpdateRect dwa miejsca stare i nowe gdzie znajdowała się postać.(zauważ ,że nie musisz odświeżać całego ekranu tylko miejsca które zostały zmienione) Tak właśnie możesz osiągnąć maksymalną wydajność w SDL. Ogólnie mówiąc powinieneś zauważyć ,że nie czyścisz już całego ekranu i odświeżasz go cały czas na daremnie. Tylko robisz to wtedy kiedy jest to konieczne.

0

@robcio podał rady wyglądające na mądre, taka uwaga jeszcze:

    while (!wyjscie) 
        ProcessEvents();
 
        // time update
        int dt=SDL_GetTicks()-czas;
        czas=SDL_GetTicks();

Najprawdopodobniej to literówka przy kopiowaniu kodu, ale zauważ że w tym co wkleiłeś while odnosi się tylko do ProcessEvents() (pętla nieskończona)!

Oraz bardzomikrooptymalizacja, jeśli Ci się chce - zamiast 2 razy wołać SDL_GetTicks() (co może powodować drobny błąd, jeśli zwrócą inną wartość) wywołuj tylko raz.

0

Bardzo dziękuję za dogłębną odpowiedź, na pewno te porady będą mi pomocne. Wciąż jednak nie rozumiem jednej rzeczy: generalnie wszystkie grafiki, itd. niezależnie jakbym je obsługiwał lądują ostatecznie na powierzchni buforu (w kodzie u mnie nazywa się on ekran), który ma rozdzielczość 320x240. Ostatecznie jednak chcę na wyjściu wyświetlać ten obraz powiększony do rozmiarów 640x480 (aby pozbyć się problemów z wyświetlaniem gry na środku ekranu na czarnym tle) - aby to zrobić, jak chłopski rozum mi podpowiada, musiałbym za każdym przebiegiem pętli głównej skalować otrzymany bufor 320x... do rozmiaru 640x... i wyświetlać go na ekranie (co czynię w przytoczonym kodzie), co powoduje wspomniany przeze mnie "wyciek pamięci" (gra może pracować kilkanaście sekund po czym kończy się miejsce w RAMie - domyślam się, że funkcje te w jakiś sposób tworzą powierzchnie, które nie są potem zwalniane) - jak to obejść? Przyznaję, że nie do końca zrozumiałem jak proponowane rozwiązania mogłyby mnie wybawić od tego problemu.

Proszę o wyrozumiałość dla mojego ciężkiego rozumowania, programistą jestem co najwyżej "niedzielnym" i SDL pierwszy raz na oczy ujrzałem nie dalej jak tydzień temu:)

Oczywiście brak klamerki przed ProcessEvents() to literówka, którą poczyniłem przy kopiowaniu kodu.

0

OK, udało mi się mniej-więcej rozwiązać mój problem. Rozciągam bufor przed rzuceniem na ekran za pomocą funkcji SDL_SoftStretch() (nie wiedzieć czemu dosyć ciężko mi było dowiedzieć się o jej istnieniu z polskiego internetu). Wydajność wciąż nie powala na kolana, ale jest już zadowalająca, więc póki co zostanę przy tym rozwiązaniu.

0

Sprawdź jeszcze czy problem dotyczy tylko tych funkcji (skalowania i blit), wywal je z głównej pętli, policz FPS, jeżeli wydajność nadal będzie kiepska to wina jest gdzieś w twoim kodzie. Jeżeli nie to wiele nie zdziałasz bo kiepska wydajność spowodowana jest funkcją blit i skalującej.

Na twoim miejscu napisałbym własną prymitywną funkcje skalującą ponieważ potrzebujesz skalowania dokładnie 2x, a tamte funkcje są "ogólne" i mają dodatkowy spory narzut.

SDL_surface pozwala na dostęp bezpośredni do pixeli poprzez pole "pixels" - http://wiki.libsdl.org/moin.cgi/SDL_Surface?highlight=%28\bCategoryStruct\b%29|%28SGStructures%29|%28SDLStructTemplate%29
Jezeli uzywasz SDL_Surface zoptymalizowanych SDL_SetSurfaceRLE dodatkowo przed dostepem do tej tablicy muszisz je zablokować a po wykonaniu operacji na tablicy pixel odblokować. http://wiki.libsdl.org/moin.cgi/SDL_Surface#Remarks

Funkcja wyglądałaby mniej wiecej tak

void scale(SDL_Surface *src, SDL_Surface *dst) {
    uint8 *srcData = (uint8*)src->pixels;
    uint8 *dstData = (uint8*)dst->pixels;

    size_t bpp = (src->BitsPerPixel >> 3)

    // kazda pozioma linia, mamy ich 240
    // linie leżą jedna za drugą wiec 320*bpp bajtów dalej stad przesuniecie srcData o 320*bpp w kazdym cyklu pętli, dstData będzie autoincrementować sie
    for(int y=0; y<240; y++, srcData += 320*bpp) {

      // 1 linia źródła == 2 linie docelowe stąd dwie iteracje kopiujące dla jednej linii.
      // mozna usunac ten for i zrobić 4x memcpy wewnątrz z odpowiednimi przesunięciami,
      // jednak tak powinno być przyjemniejsze dla cache. 
      for(int k=0; k<2; k++) {
        for(int x=0; x<320; x++)  {
            // 1 px źródłowy to teraz 2 px docelowe w poziomie wiec 2 memcpy tego samego pixela
            memcpy(dstData, srcData, bpp);
            memcpy(dstData+bpp, srcData, bpp);          

            // odpowiednie przesuniecie  
            dstData+=2*bpp; // dst o dwa pixele
            srcData+=bpp;     // src o jeden pixel
        }
        srcData-=320*bpp;  // podczas kopiowania jednej linii przesuwaliśmy wskaxnik src, zeby ponownie skopiować tę sama linię dla kolejnej linii dst, musimy sie cofnąc o 320 pixeli
      }
      // tutaj srcData jest znowu na początku tej samej linii, jednak tym razem tego nie chcemy, linia już została skopiowana dwa razy, czas na kolejną, dla tego pętla po y ma w sobie +=320*bpp
  
     } 
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1