Blitter i 2d Framebuffer w Silverlight


Przyglądając się moim ostatnim odkryciom w kwestii gier na Silverlight, a w szczególności znanej grze Quake, zacząłem się zastanawiać jak to zrobiono. Quake ma własny software’owy silnik 3D napisany przez id Software, aby działał on wydajnie w Silverlight w zasadzie jedyne co jest potrzebne to szybki dostęp do blittera i/lub framebuffera.

Jeśli przyjrzycie się bibliotece Silversprite, to zobaczycie, że taki blitter i framebuffer jest tam zaimplementowany. Silversprite jest o tyle ciekawy, że ma API bardzo bliskie XNA. Migracja projektu pomiędzy Xna a Silverlight, dzięki tej bibliotece, jest stosunkowo łatwa.

To mnie na tym etapie tak strasznie nie zainteresowało, raczej zainteresowała mnie wydajność samego blittera. Skonstruowałem sobie prosty przykład napisany przeze mnie od zera, gdzie szukałem technologicznej sposobności osadzenia bezpośredniego dostępu do pamięci ekranu (okna) w Silverlight.

Znalazłem taką sposobność i poniżej macie działający przykład na Silverlight 4.0 oraz Silverlight 3.0:

http://www.dbiesiada.com/labs/Silverlated/
http://www.dbiesiada.com/labs/Silverlated/v3/

Te przykłady, może niezbyt ładne, natomiast badają bezpośrednie wypełnienie bufora pamięci okna (czyszczenie ekranu w dwóch kolorach) oraz blit (kopiowanie) na ekran obrazów 2d i ścieżek w postaci linii prostych i krzywych beziera. W kopiowaniu wykorzystałem mechanizmy Silverlight (bez żadnej dodatkowej optymalizacji).

Ja mam SL4 zainstalowane, Tomasz Kopacz dodał mi dzisiaj rano, że ten przykład mu pokazuje, że wersja na SL4 jest szybsza niż SL3, co jest dobrym sygnałem, który jeszcze muszę zweryfikować, czekam na wasz komentarz.

Aby taki przykład zadziałał w zasadzie musicie się przyjrzeć dwóm elementom:
* Klasa WriteableBitmap, która pozwala na bezpośredni dostęp do tablicy pixeli.
* Klasa DispatcherTimer, do synchronizacji, gdzieś na jakimś forum przeczytałem, że wykorzystanie storyboardów zamiast DispatcherTimer, może jeszcze przyspieszyć sprawę, na razie tego nie testowałem. Silversprite wykorzystuje storyboardy na 100%.

Mając już te mechanizmy zaimplementowane główna pętla programowa w zasadzie sprowadza się do takiego kodu 
(http://www.wklej.eu/index.php?id=07f6cabdcb)

 if (!pause) { 
           device.Clear(redrawalCount % 2 == 0 ? 0xFF000000 : 0xFF202020); 

           for (int i = 0; i < (int)imagesCounter.Value; i++) 
           { 
            
switch (objidx) { 
            
case 0: 
                  
                  Image oscar = (Image)Application.Current.Resources["oscar"]; 
                  device.BackBuffer.Blit(oscar, randomizer.Next(640),
                         randomizer.Next(480), 
                         (
float)randomizer.NextDouble(),
                         (
float)randomizer.NextDouble(), 
                         randomizer.Next(360)); 
                  
                 
break

              case 1: 
                                     
                  LineGeometry lineGeom =
new LineGeometry(); 
                  lineGeom.StartPoint =
new Point(0,0); 
                  lineGeom.EndPoint =
new Point(0,100); 
                  Path linePath =
new Path(); 
                                     
                  linePath.Stroke =
new SolidColorBrush(Colors.White); 
                  linePath.StrokeThickness = 5; 
                  linePath.Data = lineGeom;                                           

                  device.BackBuffer.Blit(linePath, randomizer.Next(640),
                                  randomizer.Next(480), 
                                  (float)randomizer.NextDouble(),
                                  (
float)randomizer.NextDouble(), 
                                  randomizer.Next(360)); 
                                           
                  break

               case 2: 
                  
                  Path bezier = (Path)Application.Current.Resources["bezier"]; 
                  device.BackBuffer.Blit(bezier, randomizer.Next(640),
                                    randomizer.Next(480), 
                                    (
float)randomizer.NextDouble(),
                                    (
float)randomizer.NextDouble(), 
                                    randomizer.Next(360));       
                  
break;      
             }
           }
}

Device w tym przypadku to moja klasa organizująca DispatcherTimer i główną pętlę programową. Istotniejszy jest device.BackBuffer, który jest drugą klasą stworzoną przeze mnie – to po prostu wrapper na WriteableBitmap:
(http://www.wklej.eu/index.php?id=2d6a826192)

Powyższy link zawiera całą klasę, istotą tak naprawdę są dwie zaimplementowane teraz metody:

        public void Clear(uint rawColor)
        {
           
for (int i = 0; i < width * height; i++)
            {
               
unchecked
                {
                    surfaceBitmap.Pixels[i] = (
int)rawColor;
                }
            }
        }

        public void Blit(UIElement element, int xpos, int ypos,
                           
float scalex, float scaley, float angle)
        {
            TranslateTransform position =
new TranslateTransform();
            ScaleTransform scale =
new ScaleTransform();
            RotateTransform rotation =
new RotateTransform();

            position.X = xpos;
            position.Y = ypos;
            scale.ScaleX = scalex;
            scale.ScaleY = scaley;
            rotation.Angle = angle;

            TransformGroup transform = new TransformGroup();
            transform.Children.Add(position);
            transform.Children.Add(scale);
            transform.Children.Add(rotation);

            surfaceBitmap.Render(element, transform);
        }

Jedna, która na ten moment jednym kolorem wypełnia całą tablicę bitmapy. Pokazuje jak dostać się do tej tablicy z własnym kodem. Drugi to blitter wszelkich UIElement z Silverlight z fransformacjami.

Jaki FPS u was się pokazuje przy maksymalnej ilości obiektów każdego typu i na jakim sprzęcie (CPU/RAM/GPU/GPUMEM)?

Comments (7)

  1. Komodo says:

    Fx 3.5 oraz IE8, SL3 oraz SL4 – wyniki takie same.

    Figurka, 100obiektów – 26FPS. Athlon II X2 @3GHz, 4GB RAM DDR2 800MHz, karta integra Radeon 3200 bez własnej pamięci, Windows 7 x64,

  2. U mnie na Lenovo T61p,SL4

    100 oscarów: 23FPS

    100 linii: 45FPS

    100 wektorów: 32FPS

    SL3 wciąż nie sprawdzałem. Testowałeś obie wersje na runtime 4.0 czy odinstalowałeś SL4 i zainstalowałeś SL3 aby sprawdzić wersję v3?

  3. Komodo says:

    Najpierw SL3, potem upgrade do SL4.

  4. p1 says:

    100 oskarów – 47 fps

    100 linii – 88fps

    100 wektorów – 74fps

    sl3, bo taki mam ;).

    sprzęcior, i5/4GB/gtx260/1.8GB win7x64. co ciekawe, przy zwiększeniu ilości obiektów framerate… wzrósł. Potem dopiero zaczął opadać – pewnie wina turbo mode w i5 ;]

  5. p1 says:

    jeszcze przeprowadziłem kilka eksperymentów: (beziery)

    obiekty | fps

    1 – 65 fps

    25 – 65 fps

    50 – 102fps

    75 – 85fps

    100 – 74fps

    czyli, im więcej tym szybciej :]

  6. @p1:

    solidny wynik, daje do myślenia, zwłaszcza z twoimi obserwacjami dotyczącymi wzrostów i spadków.

    Ten benchmark rozwijam dalej testując inne mechanizmy z Silverlight 3.0 i bardziej już 4.0. Jak skończę (między innymi ray-tracer, który właśnie optymalizuję) to się odezwę.

  7. flatplanet says:

    100 oskarów – 42fps na: INTEL E8500 + geforce9300

    Na temat "wyższości" timera Storyboard nad DispatcherTimer można przeczytać na blogu Mike Snowa

    http://blogs.silverlight.net/blogs/msnow/archive/2008/07/09/storyboard-versus-dispatchertimer-for-animation-and-game-loops.aspx

    Świetny artykuł. Szkoda (dla mnie), że muszę przerobić renderowanie w swoim projekcie bo te jest o niebo wydajniejsze:)

Skip to main content