To Grafika.DrawImage zbyt wolny dla większych obrazów?

Obecnie pracuję nad grą i chcę mieć Menu główne z obrazem tła.

Uważam jednak, że metoda Graphics.DrawImage() jest bardzo powolna. Dokonałem pomiaru. Załóżmy, że MenuBackground to mój obraz zasobu o rozdzielczości 800 x 1200 pikseli. Narysuję ją na innej bitmapie 800 x 1200 (najpierw renderuję wszystko do bitmapy buforowej, potem skaluję i na koniec rysuję na ekranie - tak sobie radzę z możliwością rozdzielczości wielu graczy. Ale to nie powinno to w żaden sposób wpływać na to, patrz następny akapit).

Więc zmierzyłem następujący kod:

Stopwatch SW = new Stopwatch();
SW.Start();

// First let's render background image into original-sized bitmap:

OriginalRenderGraphics.DrawImage(Properties.Resources.MenuBackground,
   new Rectangle(0, 0, Globals.OriginalScreenWidth, Globals.OriginalScreenHeight));

SW.Stop();
System.Windows.Forms.MessageBox.Show(SW.ElapsedMilliseconds + " milliseconds");

Wynik jest dla mnie cichym zaskoczeniem - Stopwatch mierzy coś pomiędzy 40 - 50 milliseconds. A ponieważ obraz tła nie jest jedyną rzeczą do narysowania, całe menu zajmuje około 100 ms do wyświetlenia, co implikuje obserwowalne opóźnienie.

Próbowałem narysować go do obiektu graficznego podanego przez Zdarzenie Paint, ale wynik był 30 - 40 milliseconds - niewiele się zmieniło.

Więc, czy to znaczy, że Graphics.DrawImage() nie nadaje się do rysowania większych obrazów? Jeśli tak, co powinienem zrobić, aby poprawić wydajność mojej gry?

Author: Miroslav Mares, 2012-06-13

3 answers

Tak, jest za wolno.

[33]}napotkałem ten problem kilka lat temu podczas opracowywania Paint.NET (właściwie od samego początku i to było dość frustrujące!). Wydajność renderowania była fatalna, ponieważ zawsze była proporcjonalna do rozmiaru bitmapy, a nie do rozmiaru obszaru, który miał zostać przerysowany. Oznacza to, że liczba klatek na sekundę spadła wraz ze wzrostem rozmiaru bitmapy, a liczba klatek na sekundę nigdy nie wzrosła, gdy zmniejszył się rozmiar nieprawidłowego / przerysowanego obszaru podczas implementacji OnPaint() i wywoływanie Grafiki.DrawImage (). Mała bitmapa, powiedzmy 800x600, zawsze działała dobrze, ale większe obrazy (np. 2400x1800) były bardzo wolne. (Można założyć, dla poprzedniego akapitu i tak, że nic więcej się nie dzieje, takie jak skalowanie z jakimś drogim filtrem Bicubic, co miałoby negatywny wpływ na wydajność.)

Możliwe jest zmuszenie WinForms do używania GDI zamiast GDI+ i uniknięcie nawet tworzenia Graphics obiektu Za kulisami, w którym to momencie można warstwować inny zestaw narzędzi do renderowania (np. Direct2D). Nie jest to jednak proste. Robię to w Paint.NET i możesz zobaczyć, co jest wymagane, używając czegoś takiego jak reflektor na klasie o nazwie GdiPaintControl W DLL SystemLayer, ale za to, co robisz, uznałbym to za ostateczność.

Jednak Rozmiar bitmapy, której używasz (800x1200), powinien nadal działać wystarczająco dobrze w GDI+ bez konieczności uciekania się do zaawansowanego interop, chyba że celujesz w coś tak niskiego, jak Pentium II 300MHz. Oto kilka wskazówki, które mogą pomóc:

  • jeśli używasz nieprzezroczystej bitmapy (bez Alfy/przezroczystości) w wywołaniu Graphics.DrawImage(), a zwłaszcza jeśli jest to 32-bitowa bitmapa z kanałem alfa (ale wiesz, że jest nieprzezroczysta, lub ci to nie przeszkadza), ustaw Graphics.CompositingMode na CompositingMode.SourceCopy przed wywołaniem DrawImage() (pamiętaj, aby ustawić ją z powrotem do oryginalnej wartości po, w przeciwnym razie zwykłe prymitywy rysowania będą wyglądać bardzo brzydko). To pomija wiele dodatkowych mieszania matematyki na piksel.
  • upewnij się, że Graphics.InterpolationMode nie jest ustawione na coś w stylu InterpolationMode.HighQualityBicubic. Użycie NearestNeighbor będzie najszybsze, chociaż jeśli jest jakieś rozciąganie, może nie wyglądać zbyt dobrze (chyba że jest rozciąganie dokładnie o 2x, 3x, 4x itp.) Bilinear to zazwyczaj dobry kompromis. Nigdy nie należy używać niczego poza NearestNeighbor, Jeśli rozmiar mapy bitowej pasuje do obszaru, na którym rysujesz, w pikselach.
  • zawsze rysuj Graphics obiekt podany w OnPaint().
  • zawsze rysuj w OnPaint. Jeśli potrzebujesz przerysować obszar, zadzwoń Invalidate(). Jeśli potrzebujesz rysunku do zadzwoń Update() po Invalidate(). Jest to rozsądne podejście, ponieważ komunikaty WM_PAINT (co skutkuje wywołaniem OnPaint()) są komunikatami o niskim priorytecie. Każde inne przetwarzanie przez menedżera okien zostanie wykonane jako pierwsze, a tym samym możesz skończyć z dużą ilością pomijania i zaczepiania klatek w przeciwnym razie.
  • używanie System.Windows.Forms.Timer jako timera framerate/tick nie będzie działać zbyt dobrze. Są one zaimplementowane przy użyciu SetTimer Win32 i skutkują komunikatami WM_TIMER, które następnie powodują Zdarzenie Timer.Tick raised, a WM_TIMER jest kolejną wiadomością o niskim priorytecie, która jest wysyłana tylko wtedy, gdy kolejka komunikatów jest pusta. Lepiej użyć System.Threading.Timer, a następnie użyć Control.Invoke() (aby upewnić się, że jesteś na właściwym wątku!) i wywołanie Control.Update().
  • ogólnie nie stosować Control.CreateGraphics(). (następstwem "zawsze rysuj OnPaint()" i " Zawsze używaj Graphics, które otrzymałeś od OnPaint()')
  • zalecam nie używać Paint event handler. Zamiast tego zaimplementuj OnPaint() w klasie, z której piszesz, która powinna pochodzić Control. Wywodzące się z innej klasy, np. PictureBox lub UserControl, albo nie dodadzą żadnej wartości dla ciebie, albo dodadzą dodatkowy narzut. (BTW PictureBox jest często źle rozumiany. Prawdopodobnie prawie nigdy nie będziesz chciał go używać.)
Mam nadzieję, że to pomoże.
 89
Author: Rick Brewster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-14 01:01:39

GDI + to chyba nie najlepszy wybór do gier. DirectX / XNA lub OpenGL powinny być preferowane, ponieważ wykorzystują one niezależnie przyspieszenie grafiki jest możliwe i są bardzo szybkie.

 3
Author: Ani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-13 18:08:41

GDI + w żaden sposób nie jest demonem prędkości. Każda poważna manipulacja obrazem zwykle musi przejść na natywną stronę rzeczy(wywołania pInvoke i / lub manipulacja za pomocą wskaźnika uzyskanego przez wywołanie LockBits.)

Sprawdziłeś XNA / DirectX / OpenGL?. Są to frameworki przeznaczone do tworzenia gier i będą o rząd wielkości bardziej wydajne i elastyczne niż korzystanie z interfejsu użytkownika, takiego jak WinForms lub WPF. Wszystkie biblioteki oferują wiązania C#.

Można przypiąć do kod natywny, wykorzystujący funkcje takie jak BitBlt , ale istnieje również narzut związany z przekraczaniem granicy kodu zarządzanego.

 3
Author: Ed S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-13 19:01:31