Optimalisasi rendering untuk Seluler

Halo pembaca yang budiman, pecinta dan profesional pemrograman grafis! Kami menyampaikan kepada Anda serangkaian artikel yang ditujukan untuk optimalisasi rendering untuk perangkat seluler: ponsel dan tablet berbasis iOS dan Android. Siklus akan terdiri dari tiga bagian. Pada bagian pertama, kita akan memeriksa fitur-fitur arsitektur ubin GPU yang populer di Mobile . Dalam yang kedua, kita akan membahas keluarga utama GPU yang disajikan dalam perangkat modern, dan mempertimbangkan kekuatan dan kelemahan mereka. Pada bagian ketiga, kita akan berkenalan dengan fitur optimasi shader.

Jadi, mari kita ke bagian pertama.

Pengembangan kartu video pada desktop dan konsol terjadi tanpa adanya batasan yang signifikan pada konsumsi daya. Dengan munculnya kartu video untuk perangkat seluler, para insinyur dihadapkan dengan tugas untuk memastikan kinerja yang dapat diterima pada resolusi desktop yang sebanding, sementara konsumsi daya kartu video tersebut harus 2 kali lipat lebih rendah. 



Solusinya ditemukan dalam arsitektur khusus yang disebut Tile Based Rendering (TBR) . Bagi seorang programmer grafis yang berpengalaman dalam pengembangan PC, ketika ia berkenalan dengan pengembangan mobile, semuanya tampak akrab: OpenGL ES API serupa digunakan, struktur yang sama dari pipa grafis. Namun, arsitektur ubin GPU seluler secara signifikan berbeda dari yang digunakan pada konsol Mode PC / Segera . Mengetahui kekuatan dan kelemahan TBR akan membantu Anda membuat keputusan yang tepat dan mendapatkan kinerja hebat dengan Mobile.

Di bawah ini adalah diagram yang disederhanakan dari pipa grafis klasik yang digunakan pada PC dan konsol untuk dekade ketiga.


Pada tahap pemrosesan geometri, atribut vertex dibaca dari memori video GPU. Setelah berbagai transformasi (Vertex Shader), primitif siap-render dalam urutan asli (FIFO) diteruskan ke rasterizer, yang membagi primitif menjadi piksel. Setelah itu, langkah pemrosesan fragmen dari setiap piksel (Fragment Shader) dilakukan , dan nilai warna yang diperoleh ditulis ke buffer layar, yang juga terletak di memori video. Fitur arsitektur tradisional "Mode Segera" adalah rekaman hasil Fragmen Shader di bagian sewenang-wenang buffer layar saat memproses satu panggilan undian.. Dengan demikian, untuk setiap panggilan undian, akses ke seluruh buffer layar mungkin diperlukan. Bekerja dengan array memori yang besar membutuhkan bandwidth bus yang sesuai ( bandwidth ) dan dikaitkan dengan konsumsi daya yang tinggi. Karena itu, GPU seluler mulai mengambil pendekatan yang berbeda. Pada arsitektur ubin khas kartu video seluler, rendering dilakukan dalam sepotong kecil memori yang sesuai dengan bagian layar - ubin. Ukuran ubin yang kecil (mis. 16x16 piksel untuk kartu video Mali, 32x32 untuk PowerVR) memungkinkan Anda untuk menempatkannya langsung pada chip kartu video, yang membuat kecepatan akses sebanding dengan kecepatan akses ke register inti shader, mis. sangat cepat.


Namun, karena primitif dapat jatuh ke bagian sewenang-wenang dari buffer layar, dan ubin hanya mencakup sebagian kecil saja, diperlukan langkah tambahan dalam pipa grafis. Berikut ini adalah diagram yang disederhanakan tentang cara kerja pipa dengan arsitektur ubin.


Setelah memproses simpul dan membangun primitif, yang terakhir, bukannya dikirim ke pipa fragmen, jatuh ke dalam apa yang disebut Tiler . Di sini primitif didistribusikan oleh ubin, ke dalam piksel yang jatuh. Setelah distribusi seperti itu, yang, sebagai suatu peraturan, mencakup semua panggilan draw yang diarahkan ke satu Frame Buffer Object (alias Render Target ), ubin akan secara berurutan dirender. Untuk setiap ubin, urutan tindakan berikut dilakukan:

  1. Memuat konten FBO lama dari memori sistem ( Memuat
  2. Membuat primitif jatuh ke ubin ini
  3. Mengunggah konten FBO baru ke memori sistem ( Store )


Perlu dicatat bahwa operasi Load dapat dianggap sebagai superposisi tambahan dari "tekstur layar penuh" tanpa kompresi. Jika memungkinkan, hindari operasi ini, mis. Jangan izinkan FBO untuk bolak-balik. Jika semua kontennya dibersihkan sebelum rendering di FBO , operasi Load tidak dilakukan. Namun, untuk mengirim sinyal yang benar ke pengemudi, parameter pembersihan tersebut harus memenuhi kriteria tertentu:

  1. Harus dinonaktifkan Scissor Rect
  2. Merekam di semua saluran warna dan alfa harus diizinkan.

Untuk mencegah operasi Muatan untuk buffer kedalaman dan stensil, mereka juga perlu dibersihkan sebelum dimulainya rendering.

Dimungkinkan juga untuk menghindari operasi Store untuk buffer kedalaman / stensil. Lagi pula, isi buffer ini tidak ditampilkan dengan cara apa pun di layar. Sebelum operasi glSwapBuffers , Anda dapat menghubungi glDiscardFramebufferEXT atau glInvalidateFramebuffer

const GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
glDiscardFramebufferEXT (GL_FRAMEBUFFER, 2, attachments);

const GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, attachments);

Ada skenario rendering di mana penempatan buffer kedalaman / stensil, serta buffer MSAA dalam memori sistem tidak diperlukan. Misalnya, jika rendering dalam FBO dengan buffer kedalaman kontinu, dan informasi kedalaman dari frame sebelumnya tidak digunakan, maka buffer kedalaman tidak perlu dimuat ke dalam memori petak sebelum dimulainya rendering, atau dibongkar setelah selesai rendering. Oleh karena itu, memori sistem tidak dapat dialokasikan di bawah buffer kedalaman. API grafik modern seperti Vulkan dan Metal memungkinkan Anda untuk secara eksplisit mengatur mode memori untuk rekan-rekan FBO Anda  ( MTLStorageModeMemoryless in Metal, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT + VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT di Vulkan ).

Yang perlu diperhatikan adalah implementasi MSAA pada arsitektur ubin. Buffer resolusi tinggi untuk MSAA tidak meninggalkan memori ubin dengan memisahkan FBO ke ubin lainnya. Misalnya, untuk MSAA 2x2, ubin 16x16 akan diselesaikan sebagai 8x8 selama operasi Store , mis. Secara total, akan diperlukan untuk memproses ubin 4 kali lebih banyak. Tetapi memori tambahan untuk MSAA tidak diperlukan, dan karena rendering dalam memori ubin cepat tidak akan ada batasan bandwidth yang signifikan . Namun gunakanMSAA pada arsitektur ubin meningkatkan beban pada Tiler, yang dapat memengaruhi kinerja rendering adegan secara negatif dengan banyak geometri.

Meringkas di atas, kami menyajikan skema yang diinginkan bekerja dengan FBO pada arsitektur ubin:

// 1.   ,    auxFBO
glBindFramebuffer(GL_FRAMEBUFFER, auxFBO);
glDisable(GL_SCISSOR);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
// glClear,     
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | 
           GL_STENCIL_BUFFER_BIT);

renderAuxFBO();         

//   /      
glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, depth_and_stencil);
// 2.   mainFBO
glBindFramebuffer(GL_FRAMEBUFFER, mainFBO);
glDisable(GL_SCISSOR);

glClear(...);
//   mainFBO    auxFBO
renderMainFBO(auxFBO);

glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, depth_and_stencil);

Jika Anda beralih ke rendering auxFBO di tengah formasi mainFBO , Anda bisa mendapatkan operasi Load & Store yang tidak perlu , yang secara signifikan dapat meningkatkan waktu pembentukan frame. Dalam praktik kami, kami mengalami perlambatan dalam merender bahkan dalam kasus pengaturan FBO idle, mis. tanpa membuat aktual di dalamnya. Karena arsitektur mesin, sirkuit lama kami tampak seperti ini:

//   mainFBO
glBindFramebuffer(GL_FRAMEBUFFER, mainFBO);
//   
glBindFramebuffer(GL_FRAMEBUFFER, auxFBO);
//  auxFBO
renderAuxFBO();

glBindFramebuffer(GL_FRAMEBUFFER, mainFBO);
//   mainFBO
renderMainFBO(auxFBO);

Meskipun kurangnya panggilan gl setelah instalasi pertama mainFBO , pada beberapa perangkat kami mendapat operasi Load & Store ekstra dan kinerja yang lebih buruk.

Untuk meningkatkan pemahaman kami tentang overhead dari menggunakan FBO perantara , kami mengukur kehilangan waktu untuk beralih FBO layar penuh menggunakan tes sintetis. Tabel menunjukkan waktu yang dihabiskan untuk operasi Store ketika beralih FBO beberapa kali dalam satu frame (waktu satu operasi seperti itu diberikan). Tidak ada operasi pemuatan karena glClear, yaitu skenario yang lebih menguntungkan diukur. Izin yang digunakan pada perangkat berkontribusi. Itu bisa kurang lebih sesuai dengan kekuatan GPU yang diinstal. Oleh karena itu, angka-angka ini hanya memberikan gambaran umum tentang seberapa mahal peralihan target pada kartu video seluler dari berbagai generasi.
GPUmilidetikGPUmilidetik
Adreno 3205.2
Adreno 5120,74
PowerVR G62003.3Adreno 6150,7
Mali-4003.2Adreno 5300,4
Mali-t7201.9Mali-g510,32
PowerVR SXG 5441.4Mali-t830
0,15

Berdasarkan data yang diperoleh, kami dapat merekomendasikan untuk tidak menggunakan lebih dari satu atau dua sakelar FBO per frame, setidaknya untuk kartu video yang lebih lama. Jika game memiliki kode akses terpisah untuk perangkat Low-End, disarankan untuk tidak menggunakan perubahan FBO di sana. Namun, pada Low-End, masalah penurunan resolusi sering menjadi relevan. Di Android, Anda dapat menurunkan resolusi rendering tanpa menggunakan FBO perantara dengan memanggil SurfaceHolder.setFixedSize ():

surfaceView.getHolder().setFixedSize(...)

Metode ini tidak akan berfungsi jika game diberikan melalui aplikasi Surface utama (skema khas untuk bekerja dengan NativeActivity ). Jika Anda menggunakan Permukaan utama , resolusi lebih rendah dapat ditetapkan dengan memanggil fungsi asli ANativeWindow_setBuffersGeometry.

JNIEXPORT void JNICALL Java_com_organization_app_AppNativeActivity_setBufferGeometry(JNIEnv *env, jobject thiz, jobject surface, jint width, jint height)
{
ANativeWindow* window = ANativeWindow_fromSurface(env, surface); 
ANativeWindow_setBuffersGeometry(window, width, height, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM); 
}

Di Jawa:

private static native void setBufferGeometry(Surface surface, int width , int height )
...
//   SurfaceHolder.Callback
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
     setBufferGeometry(holder.getSurface(), 768, 1366); /* ... */
...

Terakhir, kami menyebutkan perintah ADB yang mudah untuk mengendalikan buffer permukaan yang dipilih di Android:

adb shell dumpsys surfaceflinger

Anda bisa mendapatkan kesimpulan serupa yang memungkinkan Anda memperkirakan konsumsi memori untuk buffer permukaan:


Tangkapan layar menunjukkan sistem menyoroti 3 buffer untuk buffering tiga kali lipat pada game GLSurfaceView (disorot dengan warna kuning), serta 2 buffer untuk Permukaan utama (disorot dengan warna merah). Dalam hal rendering melalui Surface utama, yang merupakan skema default saat menggunakan NativeActivity , alokasi buffer tambahan dapat dihindari. 

Itu saja untuk saat ini. Dalam artikel berikut, kami akan mengklasifikasikan GPU seluler, serta menganalisis metode untuk mengoptimalkan shader untuknya.

All Articles