Jak dokładnie działa SpriteBatch XNA?

38

Mówiąc ściślej, gdybym musiał odtworzyć tę funkcję od podstaw w innym interfejsie API (np. W OpenGL), co musiałby być w stanie zrobić?

Mam ogólne wyobrażenie o niektórych etapach, takich jak sposób przygotowania matrycy projekcji ortograficznej i tworzenia quada dla każdego wywołania losowania.

Nie jestem jednak zbyt zaznajomiony z samym procesem wsadowym. Czy wszystkie quady są przechowywane w tym samym buforze wierzchołków? Czy potrzebuje bufora indeksu? Jak obsługiwane są różne tekstury?

Jeśli to możliwe, byłbym wdzięczny, gdybyś mógł poprowadzić mnie przez ten proces od momentu wywołania SpriteBatch.Begin () do SpriteBatch.End (), przynajmniej przy użyciu domyślnego trybu odroczonego.

David Gouveia
źródło
Przyjrzeć się, jak to jest realizowane w MonoGame poprzez OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/...
Den
Czy próbowałeś użyć DotPeek lub Reflector na Microsoft.Xna.Graphics (chociaż nie sugeruję, aby robić coś nielegalnego :)?
Den
Dzięki za link. Ta myśl przeszła mi przez głowę (mam tu nawet dotPeek), ale pomyślałem, że lepiej byłoby poprosić o ogólny opis od kogoś, kto już to zrobił wcześniej i zna procedurę na pamięć. W ten sposób odpowiedź zostałaby zachowana tutaj i udostępniona innym osobom. W przypadku, gdy nikt nie dostarczy takiego opisu, sam dokonam analizy i opublikuję odpowiedź na moje pytanie, ale na razie poczekam jeszcze trochę.
David Gouveia,
Tak, dlatego użyłem komentarza. Myślę, że Andrew Russell może być dobrą osobą, aby odpowiedzieć na to pytanie. Jest aktywny w tej społeczności i pracuje nad ExEn, który jest alternatywą dla MonoGame: andrewrussell.net/exen .
Den
Tak, to jest pytanie, na które Andrew Russel (lub Shawn Hargreaves z powrotem na forum XNA) zwykle byłby w stanie zapewnić dużo wglądu.
David Gouveia,

Odpowiedzi:

42

W pewnym sensie odtworzyłem zachowanie SpriteBatch w trybie odroczonym dla silnika wieloplatformowego, nad którym pracuję, więc oto kroki, które do tej pory opracowałem metodą wsteczną:

  1. SpriteBatch konstruktor: tworzy DynamicIndexBuffer, DynamicVertexBufferoraz szereg VertexPositionColorTextureo ustalonej wielkości (w tym przypadku, maksymalna wielkość partii - 2048 i 8192 dla ikonek na wierzchołkach).

    • Bufor indeksu jest wypełniony indeksami wierzchołków quadów, które zostaną narysowane (0-1-2, 0-2-3, 4-5-6, 4-6-7 i tak dalej).
    • SpriteInfoTworzona jest również wewnętrzna tablica struktur. Spowoduje to zapisanie tymczasowych ustawień sprite, które będą używane podczas grupowania.
  2. SpriteBatch.Begin: wewnętrznie przechowuje wartości BlendState, SamplerStateitp określone i sprawdza, czy została ona wywołana dwukrotnie bez SpriteBatch.Endpomiędzy.

  3. SpriteBatch.Draw: pobiera wszystkie informacje o ikonce (tekstura, położenie, kolor) i kopiuje je do pliku SpriteInfo. Po osiągnięciu maksymalnego rozmiaru partii rysowana jest cała partia, aby zrobić miejsce dla nowych duszków.

    • SpriteBatch.DrawStringpo prostu wydaje znak Drawdla każdego znaku łańcucha, biorąc pod uwagę kerning i odstępy.
  4. SpriteBatch.End: wykonuje następujące operacje:

    • Ustawia stany renderowania określone w Begin.
    • Tworzy matrycę projekcji ortograficznej.
    • Stosuje SpriteBatchmoduł cieniujący.
    • Wiąże DynamicVertexBufferi DynamicIndexBuffer.
    • Wykonuje następującą operację grupowania:

      startingOffset = 0;
      currentTexture, oldTexture = null;
      
      // Iterate through all sprites
      foreach SpriteInfo in SpriteBuffer
      {
          // Store sprite index and texture
          spriteIndex = SpriteBuffer.IndexOf(SpriteInfo);
          currentTexture = SpriteInfo.Texture;
      
          // Issue draw call if batch count > 0 and there is a texture change
          if (currentTexture != oldTexture)
          {
              if (spriteIndex > startingOffset)
              {
                  RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                              spriteIndex - startingOffset);
              }
              startingOffset = spriteIndex;
              oldTexture = currentTexture;
          }
      }
      
      // Draw remaining batch and clear the sprite data
      RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                  SpriteBuffer.Count - startingOffset);
      SpriteBuffer.Clear();
      
  5. SpriteBatch.RenderBatch: wykonuje następujące operacje dla każdej z SpriteInfopartii:

    • Pobiera pozycję duszka i oblicza ostateczną pozycję czterech wierzchołków zgodnie z początkiem i rozmiarem. Stosuje istniejący obrót.
    • Oblicza współrzędne UV i stosuje określone SpriteEffectsdla nich.
    • Kopiuje kolor duszka.
    • Wartości te są następnie przechowywane w tablicy VertexPositionColorTexturewcześniej utworzonych elementów. Po obliczeniu wszystkich ikonek SetDatawywoływany jest DynamicVertexBufferi DrawIndexedPrimitiveswywoływane.
    • Moduł cieniujący wierzchołek wykonuje tylko operację transformacji, a moduł cieniujący pikseli stosuje zabarwienie koloru pobranego z tekstury.
r2d2rigo
źródło
Właśnie tego potrzebowałem, wielkie dzięki! Trochę mnie to pochłonęło, ale myślę, że w końcu dostałem wszystkie szczegóły. Jednym, który przykuł moją uwagę i nie byłem do końca tego świadomy, jest to, że nawet w trybie odroczenia, jeśli mogę w jakiś sposób posortować moje wywołania SpriteBatch.Draw według tekstury (tj. Jeśli nie dbam o kolejność tych wywołań), to będzie zmniejszyć liczbę operacji RenderBatch i prawdopodobnie przyspieszyć renderowanie.
David Gouveia,
Nawiasem mówiąc, nie mogę znaleźć w dokumentacji - jaki jest limit wywołań Draw, które możesz wykonać, zanim bufor się zapełni? I czy wywołanie DrawText wypełnia ten bufor całą liczbą znaków w ciągu?
David Gouveia,
Imponujący! Jak Twój silnik wypada w porównaniu z ExEn i MonoGame? Czy będzie wymagał także MonoTouch i MonoAndroid? Dzięki.
Den
@DavidGouveia: 2048 duszków to maksymalny rozmiar partii - zredagowano odpowiedź i dodano ją dla wygody. Tak, sortowanie według tekstury i pozwolenie, by bufor Z zajął się porządkowaniem ikonek, użyłoby minimalnej liczby wywołań rysujących. Jeśli potrzebujesz, spróbuj SpriteSortMode.Texturenarysować w dowolnej kolejności i pozwól SpriteBatchsortować.
r2d2rigo,
1
@Den: ExEn i MonoGame to interfejsy API niższego poziomu, które umożliwiają uruchamianie kodu XNA na platformie innej niż Windows. Projekt, nad którym pracuję, to silnik oparty na komponentach napisany wyłącznie w języku C #, ale potrzebujemy bardzo cienkiej warstwy, aby zapewnić dostęp do interfejsów API systemu (nad tym pracuję). Po prostu zdecydowaliśmy się na ponowną implementację SpriteBatchdla wygody. I tak, wymaga MonoTouch / MonoDroid, ponieważ jest to wszystko C #!
r2d2rigo,
13

Chcę tylko zaznaczyć, że kod SpriteBatch XNA został przeniesiony do DirectX i wydany jako OSS przez poprzedniego członka zespołu XNA. Jeśli więc chcesz dokładnie zobaczyć, jak to działa w XNA , proszę bardzo.

ClassicThunder
źródło
+1 za „kod, którego nigdy nie użyję, ale jest ciekawy”
Kyle Baran,
Najnowszą wersję XNA SpriteBatch jako natywnego C ++ można znaleźć na GitHub h / cpp . Jako bonus dostępna jest również wersja DirectX12 h / cpp
Chuck Walbourn