Bagaimana tepatnya cara kerja SpriteBatch XNA?

38

Untuk lebih tepatnya, jika saya perlu membuat ulang fungsi ini dari awal di API lain (mis. Di OpenGL) apa yang perlu dilakukan?

Saya memang memiliki gagasan umum tentang beberapa langkah, seperti bagaimana mempersiapkan matriks proyeksi ortografis dan membuat quad untuk setiap panggilan draw.

Saya tidak terlalu akrab dengan proses batching itu sendiri. Apakah semua paha depan disimpan dalam buffer vertex yang sama? Apakah perlu buffer indeks? Bagaimana berbagai tekstur ditangani?

Jika memungkinkan, saya akan berterima kasih jika Anda dapat membimbing saya melalui proses dari saat SpriteBatch.Begin () dipanggil hingga SpriteBatch.End (), setidaknya ketika menggunakan mode Deferred default.

David Gouveia
sumber
Lihat bagaimana penerapannya di MonoGame via OpenGL: github.com/mono/MonoGame/blob/master/MonoGame.Framework/…
Den
Apakah Anda mencoba menggunakan DotPeek atau Reflector di Microsoft.Xna.Graphics (saya tidak menyarankan untuk melakukan sesuatu yang ilegal :)?
Den
Terima kasih untuk tautannya. Dan pikiran itu memang melintas di benakku (aku bahkan punya dotPeek di sini), tapi kupikir mungkin ide yang lebih baik untuk meminta deskripsi umum dari seseorang yang mungkin sudah melakukan itu sebelumnya dan mengetahui prosedurnya dengan ingatan. Dengan begitu jawabannya akan dipertahankan di sini dan tersedia untuk orang lain. Dalam hal tidak ada yang memberikan deskripsi seperti itu, saya akan melakukan analisis sendiri dan mengirim jawaban untuk pertanyaan saya sendiri, tetapi saya akan menunggu sedikit lebih banyak untuk saat ini.
David Gouveia
Ya, itulah alasan saya menggunakan komentar. Saya pikir Andrew Russell mungkin orang yang baik untuk menjawab pertanyaan ini. Dia aktif di komunitas ini dan sedang mengerjakan ExEn yang merupakan alternatif untuk MonoGame: andrewrussell.net/exen .
Den
Ya, ini adalah jenis pertanyaan yang Andrew Russel (atau Shawn Hargreaves kembali di forum XNA) biasanya dapat memberikan banyak wawasan.
David Gouveia

Jawaban:

42

Saya telah mereplikasi perilaku SpriteBatch dalam mode ditangguhkan untuk mesin lintas-platform yang saya kerjakan, jadi inilah langkah-langkah yang telah saya rekayasa terbalik sejauh ini:

  1. Konstruktor SpriteBatch: membuat a DynamicIndexBuffer, DynamicVertexBufferdan array VertexPositionColorTextureukuran tetap (dalam hal ini, ukuran batch maksimum - 2048 untuk sprite dan 8192 untuk simpul).

    • Buffer indeks diisi dengan indeks vertex paha depan yang akan ditarik (0-1-2, 0-2-3, 4-5-6, 4-6-7 dan seterusnya).
    • Array internal SpriteInfostruct juga dibuat. Ini akan menyimpan pengaturan sprite sementara untuk digunakan saat batching.
  2. SpriteBatch.Begin: internal menyimpan nilai-nilai BlendState, SamplerState, dll ditentukan dan memeriksa apakah telah disebut dua kali tanpa SpriteBatch.Enddi antara.

  3. SpriteBatch.Draw: mengambil semua info sprite (tekstur, posisi, warna) dan menyalinnya ke a SpriteInfo. Jika ukuran bets maks tercapai, seluruh bets diambil untuk memberikan ruang bagi sprite baru.

    • SpriteBatch.DrawStringhanya mengeluarkan a Drawuntuk setiap karakter string, dengan mempertimbangkan kerning dan spasi.
  4. SpriteBatch.End: melakukan operasi berikut:

    • Menetapkan status render yang ditentukan dalam Begin.
    • Membuat matriks proyeksi ortografis.
    • Terapkan SpriteBatchshader.
    • Mengikat DynamicVertexBufferdan DynamicIndexBuffer.
    • Lakukan operasi pengumpulan berikut:

      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: menjalankan operasi berikut untuk masing-masing SpriteInfodalam batch:

    • Mengambil posisi sprite dan menghitung posisi akhir dari empat simpul sesuai dengan asal dan ukuran. Menerapkan rotasi yang ada.
    • Menghitung koordinat UV dan berlaku yang ditentukan SpriteEffectsuntuk mereka.
    • Menyalin warna sprite.
    • Nilai-nilai ini kemudian disimpan dalam array VertexPositionColorTextureelemen yang sebelumnya dibuat. Ketika semua sprite telah dihitung, SetDatadipanggil DynamicVertexBufferdan DrawIndexedPrimitivespanggilan dikeluarkan.
    • Vertex shader hanya melakukan operasi tranform, dan pixel shader menerapkan pewarnaan pada warna yang diambil dari tekstur.
r2d2rigo
sumber
Itulah yang saya butuhkan, terima kasih banyak! Butuh saya sedikit untuk menyerap semua itu, tapi saya pikir saya akhirnya mendapatkan semua detailnya. Salah satu yang menarik perhatian saya dan saya tidak sepenuhnya menyadari sebelumnya adalah bahwa bahkan dalam mode ditunda, jika saya entah bagaimana dapat mengurutkan panggilan SpriteBatch.Draw saya dengan Tekstur (yaitu jika saya tidak peduli tentang urutan panggilan ini) itu akan mengurangi jumlah operasi RenderBatch, dan mungkin mempercepat rendering.
David Gouveia
Ngomong-ngomong, sepertinya saya tidak dapat menemukan di dokumentasi - berapa batas Draw call yang dapat Anda lakukan sebelum buffer penuh? Dan apakah memanggil DrawText mengisi buffer itu dengan seluruh jumlah karakter dalam string?
David Gouveia
Impresif! Bagaimana mesin Anda dibandingkan dengan ExEn dan MonoGame? Apakah akan membutuhkan MonoTouch dan MonoAndroid juga? Terima kasih.
Den
@ DavidVouveia: 2048 sprite adalah ukuran kumpulan maksimal - diedit jawabannya dan ditambahkan untuk kenyamanan. Ya, mengurutkan berdasarkan tekstur dan membiarkan buffer-z menangani pemesanan sprite akan menggunakan panggilan undian minimum. Jika Anda membutuhkannya, cobalah menerapkan SpriteSortMode.Textureuntuk menggambar dalam urutan apa pun yang Anda inginkan dan biarkan SpriteBatchmelakukan penyortiran.
r2d2rigo
1
@Den: ExEn dan MonoGame adalah API tingkat rendah yang memungkinkan kode XNA berjalan di platform non-Windows. Proyek yang saya kerjakan adalah mesin berbasis komponen yang murni ditulis dalam C #, tetapi kita membutuhkan lapisan yang sangat tipis untuk menyediakan akses ke API sistem (di situlah saya sedang mengerjakan). Kami hanya memilih untuk mengimplementasikan ulang SpriteBatchdemi kenyamanan. Dan ya, itu membutuhkan MonoTouch / MonoDroid karena itu semua C #!
r2d2rigo
13

Hanya ingin menunjukkan bahwa kode XNA SpriteBatch telah porting ke DirectX dan dirilis sebagai OSS oleh anggota tim XNA sebelumnya. Jadi jika Anda ingin melihat cara kerjanya di XNA, silakan.

ClassicThunder
sumber
+1 untuk "kode yang tidak akan pernah saya gunakan tetapi menarik untuk dilewatkan"
Kyle Baran
Versi terbaru XNA SpriteBatch sebagai C ++ asli dapat ditemukan di GitHub h / cpp . Sebagai bonus, versi DirectX12 juga tersedia h / cpp
Chuck Walbourn