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)?