Text Render İyileştirmeleri

Kaptan Yosun

Co-Co Admin
Moderatör
Geliştirici
Yardımsever Üye
Usta Üye
Mesaj
1.537
Çözümler
56
Beğeni
2.928
Puan
1.839
Ticaret Puanı
0
Yıllardır Metin2'nin ilkel text renderinin performansı nasıl etkilediğini, kalabalık yerderde FPS'i nasıl dibe çektiğini gördük.

Bunun birkaç sebebi var:
  • Her harf ayrı vertex set ediliyor
  • Her harf ayrı draw call ile çiziliyor
  • Outline varsa aynı harf için 4 fazladan draw call atılıyor
  • Her harf için sürekli texture seçimi ve state değişimi yapılıyor
Bu şu anlama geliyor, ekranda 200 harflik bir yazı için, Outline açıkken karakter başına 5 draw call'dan toplam 1000 çizim isteği yollanıyor CPU'ya.
Bu sadece tek bir yazı için.

Ekranda aynı anda onlarca isim, çeşitli yazı ve UI text’i olduğunu düşünürsek, text sistemi gereksiz şekilde CPU -> GPU submission yükü oluşturuyordu. Sorun fillrate değil, draw call sayısıydı.

Ben de başlangıç noktamı temel alabilmek için bir metrik penceresi yazdım.

Screenshot 2026-02-19 043717.webp


Ekranda 135 tane text renderlayan instance ve 1742 harf varken:
FPS: 33.2
Frame Time: 30.1ms
ve tamı tamına 7618 Draw Call!

İlk iyileştirmelerim:​

Per-Instance Batching​

Bu, şu demek: Her harfi ayrı ayrı çizmek yerine;
  • Aynı font texture page’e ait karakterleri tek batch içinde topladım
  • Vertex ve index verilerini std::vector içinde biriktirdim
  • Texture page değiştiğinde veya text bittiğinde tek seferde DrawIndexedPrimitiveUP çağrısı yaptım.
Böylece harf başına 5 draw call ortadan kalktı.
Page başına 1 draw call’a düştü

Ayrıca Outline Render’ı Batch’e Dahil Ettim​

Eski sistemde outline:
  • Sol
  • Sağ
  • Üst
  • Alt
olmak üzere 4 ayrı primitive çağrısı ile çiziliyordu.
Yeni sistemde outline quad’ları da ana batch içine ekleniyor ve aynı submission içinde gönderiliyor.
Yani outline artık draw call sayısını katlamıyor.

Texture Page Bazlı Flush Mekanizması​

Font sistemi multi-page çalıştığı için:
  • Texture page değiştiğinde batch flush ediliyor.
  • Böylece mevcut davranış korunuyor.
  • Multi-page font desteği bozulmadı.

Gereksiz Reallocation’ların önüne geçmek​

Batch vector’ları:
  • reserve() ile önceden kapasite alıyor.
  • Destroy sırasında temizleniyor.
  • Frame içinde tekrar tekrar heap allocation yapılmıyor.

Sonuç:​

Screenshot 2026-02-19 054313.webp


MetrikÖnceSonraFark
FPS3036.4+21%
Frame Time32.8ms27.4ms-5.4ms
Draw Call7618255-96%
Draw / Harf4.480.15-96%
 

Aşama 2 – Geometry Cache Sistemi​


İlk aşamada draw call sayısını %96 civarında düşürmüştüm.

Ancak orada hâlâ önemli bir problem vardı:
Her frame’de bütün text geometry’si baştan hesaplanıyordu.

Yani draw call sayısı düşmüş olsa bile:
  • Her frame tüm harflerin quad’ları yeniden oluşturuluyordu
  • Outline hesaplaması tekrar yapılıyordu
  • Align, multiline, limit width, clip kontrolü sürekli çalışıyordu
  • Text değişmiyorsa bile CPU tarafında gereksiz iş yapılıyordu.
Bu yüzden ikinci aşamada hedefim:
“Text değişmediği sürece geometry yeniden build edilmesin.”

Yeni sistemde render ikiye ayrıldı:
  • __BuildGeometry() -> sadece gerektiğinde çalışıyor
  • Render() → sadece hazır geometry’yi çiziyor

Dirty Flag Mantığı​

Yeni bir flag ekledim: m_isGeometryDirty
Bu flag şu durumlarda true oluyor:
  • Text değiştiğinde
  • Renk değiştiğinde
  • Outline açılıp kapandığında
  • Align değiştiğinde
  • Position değiştiğinde
  • Limit width değiştiğinde
  • MultiLine değiştiğinde
  • Clip değiştiğinde
Yani text’i etkileyen ne varsa geometry dirty oluyor.

Eğer hiçbir şey değişmediyse:
👉 Geometry tekrar hesaplanmıyor.
👉 Direkt cache’lenmiş vertex/index buffer çiziliyor.

Geometry Artık Frame Bağımlı Değil​

Eski sistem:
Her frame:
  • Harfleri dolaş
  • Quad üret
  • Page değişimini kontrol et
  • Outline üret
  • Index yaz
  • Sonra çiz
Yeni sistem:
Sadece değişiklik olduğunda:
  • Quad’ları üret
  • Page bazlı run’ları kaydet
  • Vertex/index buffer’ı cache’le
Render tarafında ise:
  • Sadece texture seç
  • Hazır index aralığını çiz
Bu ciddi CPU tasarrufu sağladı.

Page Bazlı Run Sistemi​

Artık batch’i anında flush etmiyorum.
Onun yerine:
  • Aynı page’e ait index aralıklarını “run” olarak kaydediyorum.
  • Render aşamasında bu run’ları sırayla çiziyorum.
Bu sayede:
  • Geometry tek seferde oluşturuluyor
  • Page bazlı submission korunuyor
  • Multi-page font davranışı bozulmuyor

Clip Dependency Cache​

Build sırasında kullanılan clip top değeri cache’leniyor.
Eğer clip değişmezse geometry yeniden hesaplanmıyor.
Bu küçük ama önemli bir detay.

Screenshot 2026-02-19 160526.webp


Aşama 1'e kıyasla yeni sonuçlar:

MetrikAşama 1Aşama 2Fark
FPS36.441.5+14%
Frame Time27.4ms24.0ms-3.4ms
 
Geri
Üst