Renderowanie w SFML trwa potwornie długo — dlaczego?

1

Prosty, testowy projekt — stworzenie okna, ustawienie mu rozmiaru, załadowanie obrazka z pliku i wyświetlenie go w oknie, rozciągniętego na całego klienta. Istotny kod niżej, cały projekt z nagłówkami i binarkami w załączniku:

var
  Window: TSfmlRenderWindow;
  Event: TSfmlEvent;
  Ground: TSfmlTexture;
  Sprite: TSfmlSprite;
begin
  Window := TSfmlRenderWindow.Create(SfmlVideoMode(800, 600), 'SFML tester', [sfTitleBar, sfClose]);
  Ground := TSfmlTexture.Create('grounds\modern\game normal.bmp');
  Sprite := TSfmlSprite.Create(Ground);

  Sprite.Position := SfmlVector2f(0, 0);
  Sprite.Scale(800.0 / Sprite.LocalBounds.Width, 600.0 / Sprite.LocalBounds.Height);

  while Window.IsOpen do
  begin
    while Window.PollEvent(Event) do
    begin
      if Event.EventType = sfEvtKeyPressed then
        if Event.Key.Code = sfKeyEscape then
          Window.Close();

      if Event.EventType = sfEvtClosed then
        Window.Close();
    end;

    Window.Clear(SfmlColor(0, 0, 0));
    Window.Draw(Sprite);
    Window.Display();

    UpdateFrameRate();

    if FrameRateChanged then
      Window.SetTitle(FrameRate.ToString() + 'fps');
  end;

Taki kod wyciąga zawrotne 37fps — lodówka szybciej renderuje. Co robię źle, że trwa to aż tak długo?

2

Odpaliłem exe od Ciebie...
screenshot-20210819031312.png

"U mnie działa" :]

Czy jesteś pewien, że dobrze liczysz FPS?
Może spróbuj na start wypluwać 1.0 podzielone przez czas tylko między dwiema klatkami?
1.0/(current - previous)

0
Spine napisał(a):

Czy jesteś pewien, że dobrze liczysz FPS?

A widziałeś w jaki sposób obliczam FPS? ;)

procedure UpdateFrameRate();
begin
  FrameRateNewSecond := SecondOf(Now());
  FrameRateChanged := FrameRateNewSecond <> FrameRateSecond;

  if FrameRateChanged then
  begin
    FrameRateSecond := FrameRateNewSecond;
    FrameRate := FrameRateTank;
    FrameRateTank := 1;
  end
  else
    FrameRateTank += 1;
end;

Ten kod nie może się mylić — póki numer sekundy na zegarze systemowym się nie zmienił, inkrementuję tank (taki akumulator), a jak sekunda się zmieniła, to przepisuję wartość z tanku do właściwego licznika. Identyczne obliczenia przeprowadzam w kilku innych projektach (m.in. w Richtrisie, Fairtrisie czy Deep Platformerze), tyle że mam to opakowane w klasę. No i te same obliczenia mam w projekcie testowym dla SDL i dostaję ~700fps.

WTF z tym SFML-em… :|

1
furious programming napisał(a):

WTF z tym SFML-em… :|

Może rozwinięcie skrótu ma coś z tym wspólnego?

Super FPS Mega Limiter

0

Sprawdziłem ile czasu zajmuje obsługa jednej klatki — skacze od 15ms do 70ms. :D

0

Może czas zainstalować sterowniki do karty graficznej ;) ?

0

No jak — mam sterowniki. Coś nie halo z tym SFML, skoro dokładnie taki sam programik testowy, tyle że w SDL, osiąga kilkudziesięciokrotnie lepsze wyniki. Gdzieś jest problem, ale nie wiem gdzie.

0

A którego SDL używasz, już 2.x, czy jeszcze 1.x?
Bo chyba dopiero dwójka używa OpenGL.

0

SDL2 — nagłówki i pliki dll pobrałem z tego repo (https://github.com/CWBudde/PasSFML).

W razie czego, w załączniku to samo, tyle że z użyciem SDL. W rozdziałce 800x600 daje ~370fps. Jeśli wyłączę czyszczenie tła przed renderowaniem (czyszczenie w moim przypadku jest zbędne), to framerate wzrasta do ~460fps. Na fullscreenie (tryb okienkowy, 1280x800) mam ~205fps, a w ekskluzywnym trybie wideo ~1000fps.

Na tym laptopie mam zintegrowaną kartę graficzną, więc szału nie będzie. ;)

1

Ja mam ok. 3700fps na zintegrowanej karcie Intel 630 w procku i5-10400

1

u mnie skacze od 7600 - 8400 na SDLu i 770 - 810 na SFMLu - rząd wielkości różnicy...

1

To ja dla porównania z moim wynikiem SFML, daję jeszcze SDL...
screenshot-20210819161655.png

2

być może sprzęt/system ma znaczenie
win10 pro 20H2 19042.1110
Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
RAM 32.0 GB
grafika: Intel 630 i nVidia quadro P2000

a tu screen z wykorzystania zasobów (podobnie jest jeśli uruchomione są pojedynczo)
screenshot-20210819170948.png

1

Dzięki za testy. U mnie nie będzie dużo na żadnym API, dlatego że procesor w moim lapku (Lenovo X201 Tablet z Intel Core i7 L640) jest raczej słabiutki i o ile system działa sprawnie, różne aplikacje i małe gry działają normalnie, to bardziej wymagające gry będą się dławić. Do tego menedżer zadań nie pokazuje w ogóle żadnego GPU w zakładce Performance, więc ten sprzęt jest jakiś dziwny.

abrakadaber napisał(a):

u mnie skacze od 7600 - 8400 na SDLu i 770 - 810 na SFMLu - rząd wielkości różnicy...

No właśnie — to samo jest u mnie i zastanawiam się dlaczego. SFML renderuje 3x wolniej niż StretchDraw z LCL, a ten używa bodaj StretchBlt z WindowsAPI. Coś jest ewidentnie nie tak z tym renderowaniem.


Pobawiłem się trochę SDL-em i SFML-em, a jeszcze pobawię się biblioteką Allegro.

SDL zawiera mnóstwo różnych rzeczy, działa sprawnie. Minusem jest to, że API jest proceduralne, a także problem z ekskluzywnym trybem wideo. O ile nie ma problemu z odpaleniem gry w trybie wideo np. 800x600 na pełen ekran, tak Alt+Tab powoduje, że program zapętla się — minimalizuje okno, przywraca oryginalną rozdzielczość, następnie przywraca okno zmieniając tryb wideo i tak w kółko, przez co ekran potwornie miga i tkwi w tej pętli w nieskończoność. Trzeba zamknąć program, aby wyjść z tej pętli. Dzieje się tak u wszystkich, nie tylko u mnie.

SFML mi się najbardziej podoba, ze względu na obiektowe podejście. Jednak to API jest solidnie niedorozwinięte i aż krew się gotuje, jak trzeba zrobić podstawowe rzeczy. Chcę wyświetlić grafikę na pełne okno — Window.Draw przyjmuje jedynie obiekt sprajtu, bez współrzędnych czy obszarów źródłowego i docelowego. Muszę zmienić rozmiar sprajtu — nie mogę nadać mu nowej szerokości i wysokości, muszę obliczać mnożniki i go skalować. Chcę obsłużyć klawiaturę — nie ma normalnych scancode'ów, klawisze są opisane enumami, w dodatku ich kolejność jest z d**y, niepasująca ani do systemowych kodów wirtualnych, ani do kodowań znaków. Co do cholery? Kto tak pisze kod? Ekskluzywny tryb wideo jest obsługiwany normalnie, ale Alt+Tab o ile minimalizuje okno poprawnie, to nie przywraca rozdzielczości pulpitu i zostaje 800x600… To jeszcze byłbym w stanie samemu zabezpieczyć (przechwycić zdarzenia minimalizacji/przywracania i zmieniać rozdziałkę), ale nadal efektywność renderowania wyklucza zastosowanie tej biblioteki. I debilne, upośledzone API.

screenshot-20210819171814.png

Tak działa u mnie ten programik stojący na SFML — 37fps, CPU zjedzony.


Jeszcze mi Allegro zostało do zabawy — może to poprawnie obsłuży tryb pełnoekranowy i zapewni szybkie renderowanie.

1

u mnie aż takich różnić nie ma:

  • SDL: ~1510 fps
  • SFML: ~1280 fps

Windows 10, i7-4800, grafika intel 4600

2

Mój pomysł jest taki, że iGPU nie wyrabia narzutu związanego z trybem fullscreen (dlatego normalny działa płynnie). Te procesory nie miały dobrej opinii, to był właściwie prototyp, który Intel zdecydował się sprzedawać. Według daty produkcji ten układ graficzny ma 11 lat, ale technologicznie to już wtedy był przestarzały, a specyfikację ma jeszcze słabszą od superbudżetowych integr AMD, które wyszły w tamtym czasie.

Nie zdziwiłbym się, gdyby się okazało, że implementacja skalowania obrazu w trybie fullscreen w Windowsie i/lub w SFML była napisana na kolanie. To pojawiło się dopiero w ostatniej dekadzie i mogli załozyć, że tak prosta operacja na współczesnych układach jest tak szybka, że niewarta optymalizacji. Dlatego pozostali mają dużo mniejsze spadki i jak widać po wynikach, im lepsza karta tym mniejsza różnica w FPSach.

0

Nawet jeśli to prawda co piszesz, to nadal pozostaje jedna rzecz — różnica w wydajności SFML a SDL. Ten drugi zapierdziela, skaluje obraz bardzo szybko, pomimo tego, że używa interpolacji liniowej (a SFML wywala jałowy, rozpikselowany obraz, czyli przeprowadza najbiedniejszy typ skalowania).

Obecna wersja Fairtrisa używa systemowego StretchBlt — przy skalowaniu do 1280x800, StretchBlt + odmalowanie okna daje 130-140fps. SFML przy skalowaniu do 800x600 tej samej grafiki wyciąga ~38fps. SDL przy skalowaniu do 1280x800 z interpolacją liniową wyciąga 200fps i nawet jak dorzucę dodatkowo renderowanie dziesiątek tekstur na tylnym buforze, to framerate nie spada poniżej ~195fps (renderowanie tekstur na teksturze wygląda na wysoce wydajne). Czysto software'owe skalowanie do 1280x800 bez interpolacji daje u mnie 3-4fps — ale wyłącznie z użyciem CPU, więc nie dziwne, że jedna taka operacja trwa 200-250ms.

Z tego co sam wybadałem i co doświadczyli inni, SFML musi wykorzystywać inne mechanizmy niż SDL. I musi to robić na tyle źle, że nawet na potężnych sprzętach ma znacznie gorszą wydajność niż SDL.

1

u mnie ewidentnie wersja SDLowa ciśnie grafikę na 100% a SFMLowa na ~15% (obie działają na nVidii, nie na Intelu)

3

Dobra, panowie, bo już sam zacząłem off-top ciągnąć. Nie wiem do tej pory dlaczego SFML tak posysa, ale nie będę go używał — nie chce mi się szukać przyczyny, a robię wszystko tak jak to stoi w tutorialach. Zresztą po wynikach waszych testów jasno widać, że coś jest ewidentnie z nim nie tak.

Skorzystam z SDL, bo ten nie dość że działa wydajnie u wszystkich, to w dodatku da się w nim normalnie programować — lepiej napisać 5x więcej kodu i mieć wszystko z głowy, niż rwać włosy z głowy kombinując jak działa ta obiektowa, SFML-owa abominacja. Reasumując — problem rozwiązany, dzięki wielkie za pomoc i testy. ;)



Niżej jeszcze wyjaśnienia wcześniej podanych problemów z SDL (i rozwiązania).

Wcześniej podałem, że SDL ma problem z dezaktywacją (np. za pomocą Alt+Tab) programu odpalonego w trybie wideo (np. 640x480). Okno powinno się zminimalizować, a po przywróceniu tryb wideo powinien zostać również przywrócony (tak działają normalne gry czy emulatory), natomiast ten zapętla minimalizację i przywracanie, powodując niekończące się miganie ekranów i trzeba zamknąć program, aby to szaleństwo się skończyło.

Można to rozwiązać na dwa sposoby. Pierwszy to przechwycenie zdarzenia SDL_WINDOWEVENT_MINIMIZED, wyłączenie trybu fullscreen funkcją SDL_SetWindowFullScreen, zmiana rozmiaru okna na jakiś własny i ustawienie nowej pozycji. Natomiast drugi sposób to wyłączenie opcji SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS:

SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, '0');

Dzięki temu, SDL po dezaktywacji okna odpalonego w trybie wideo nie będzie próbował go minimalizować, a więc nie zapętli minimalizacji i przywracania. System sam zminimalizuje okno i zmieni rozdzielczość na desktopową, a po ponownej aktywacji okna programu (Alt+Tab lub po kliknięciu w przycisk programu na pasku zadań), ładnie przywróci tryb wideo.

Miałem też problem z implementacją funkcji toggle fullscreen. Po wyłączeniu trybu wideo, zmieniałem rozmiar okna na własny i centrowałem okno na głównym ekranie. Niestety SDL ani nie zmieniał rozmiaru okna (zostawało 640x480), ani nie centrował okna (zostawała pozycja 0,0). Doszedłem jednak do takiego rozwiązania, że wyjście z trybu wideo centruje okno na ekranie i w większości przypadków rozmiar okna faktycznie się zmienia. W większości przypadków.

Okno chcę mieć bez obramowania, z możliwością przeciągania łapiąc za jego klienta. Do tego użyłem funkcji SDL_SetWindowHitTest, podając własny callback, który zawsze zwraca SDL_HITTEST_DRAGGABLE. Okazuje się, że jeśli jest ustawiony callback, to nie da się ukryć kursora — nawet w trybie wideo. Dlatego aby móc w trybie okienkowym pokazać kursor, należy podłączyć callback:

SDL_SetWindowHitTest(AWindow, @MyHitTestCallback, nil);

a ustawiając okno na pełny ekran, odpinać go:

SDL_SetWindowHitTest(AWindow, nil, nil);

Śmieszne jest też to, że gdy callback jest podłączony i program działa w trybie wideo, jego okno można nadal przeciągać na inny ekran. Nie wiem dlaczego SDL/Windows na to pozwala. Dlatego lepiej ten callback odpinać przy przejściu na fullscreen, a w nim dodatkowo sprawdzać swoje flagi i zwracać wartość SDL_HITTEST_NORMAL, jeśli okno nie może być przeciągane. Czyli callback powinien wyglądać tak:

function MyHitTestCallback(AWindow: PSDL_Window; const APoint: PSDL_Point; AData: Pointer): TSDL_HitTestResult; cdecl;
begin
  if DisplaySize in [DISPLAY_FULL, DISPLAY_VIDEO] then // jeśli przeciąganie okna ma być niedostępne
    Result := SDL_HITTEST_NORMAL
  else
    Result := SDL_HITTEST_DRAGGABLE;
end;

DisplaySize to moja zmienna przechowująca obecny tryb wyświetlania. DISPLAY_FULL oznacza desktopowy tryb pełnoekranowy (zwykłe okno rozciągnięte na cały ekran), a DISPLAY_VIDEO to ekskluzywny tryb wideo — w obu tych przypadkach przeciąganie ma być zablokowane.



W razie gdyby ktoś chciał się pobawić to w załączniku dorzucam swój testowy programik, w którym wszystko powyższe jest użyte. Kod jest napisany byle jak, byle sprawdzić czy wszystko czego potrzebuję działa poprawnie. Obsługa:

Esc — zamknięcie programu,
F11 — toggle fullscreen, przejście do trybu wideo 640x480 lub powrót do trybu okienkowego,

S, R, T — odpalenie różnych dźwięków na trzech różnych kanałach audio.

Rolką myszy zmienia się rozmiar okna — 1x, 2x, 3x oraz desktopowy pełny ekran. Mała uwaga — testowałem obsługę klawiatury, czytając stan klawiszy bezpośrednio z bufora (nie zdarzeniami). Dlatego jak się przytrzyma S, R lub T, to dźwięk będzie w kółko puszczany od początku, a po zwolnieniu klawisza będzie się odgrywał do końca. Tak że to nie bug, to feature. :]

2

@furious programming: Żeby był komplet, na podstawie SFML testera zrobiłem Unity 3D Tester (Universal Rendering Pipeline).
Zobacz, jak Unity 3D się u Ciebie sprawuje ;)

Nie żebym się spodziewał lepszych wyników niż SDL (chociaż kto wie :D ), ale mam nadzieję, że będzie miał u Ciebie więcej FPS niż SFML.

Liczę FPS po Twojemu.
screenshot-20210821045405.png

1

Mniej więcej połowa wydajności SDL, choć nie używa interpolacji liniowej do skalowania. ;)

screenshot-20210821050905.png

Czyli SFML jest popsuty. Ciekawe jak raylib by sobie poradził — też są nagłówki dla Lazarusa.

1
furious programming napisał(a):

ale nie używa interpolacji liniowej do skalowania. ;)

No tak, powyłączałem wszystko, żeby były ostre piksele, bo myślałem, że ma być retro.
Ale wyświetlam dodatkowo tekst w każdej klatce z TextMeshPro :]

0

Spoko, mimo wszystko porównanie jest. Skalowanie z interpolacją nie powinno zwiększyć narzutu obliczeniowego — przynajmniej dla SDL nie ma ono żadnego znaczenia. Retro ma być, ale jednak lekkie rozmywanie grafiki zwiększa komfort, bo animacje nie wydają się takie skokowe.

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