Nintendo DS Console GPU dan Fitur Menarik


Saya ingin memberi tahu Anda tentang pengoperasian konsol Nintendo DS GPU, perbedaannya dari GPU modern, dan juga menyatakan pendapat saya tentang mengapa menggunakan Vulkan sebagai ganti OpenGL dalam emulator tidak akan membawa keuntungan.

Saya tidak benar-benar tahu Vulkan, tetapi dari apa yang saya baca, jelas bagi saya bahwa Vulkan berbeda dari OpenGL dalam hal itu bekerja di tingkat yang lebih rendah, yang memungkinkan programmer untuk mengelola memori GPU dan hal-hal serupa. Ini dapat berguna untuk meniru lebih banyak konsol modern yang menggunakan API grafis eksklusif yang menyediakan tingkat kontrol yang tidak tersedia di OpenGL.

Misalnya, penyaji perangkat keras blargSNES - salah satu triknya adalah bahwa selama beberapa operasi dengan buffer warna yang berbeda, satu buffer kedalaman / stensil digunakan. Di OpenGL, ini tidak mungkin.

Selain itu, lebih sedikit sampah yang tersisa antara aplikasi dan GPU, yang berarti bahwa jika diterapkan dengan benar, kinerja akan lebih tinggi. Sementara driver OpenGL penuh dengan optimisasi untuk kasus penggunaan standar dan bahkan untuk game tertentu, dalam Vulkan aplikasi itu sendiri harus ditulis dengan baik terlebih dahulu.

Intinya, "tanggung jawab besar datang dengan kekuatan besar."

Saya bukan spesialis 3D API, jadi mari kita kembali ke situ. Yang saya tahu benar: Konsol GPU DS.

Beberapa artikel telah ditulis tentang bagian-bagiannya masing-masing ( tentang paha depan yang canggih , tentang omong kosong dengan viewport , tentang fitur menyenangkan rasterizer dan tentang implementasi anti-aliasing yang menakjubkan ), tetapi dalam artikel ini kita akan mempertimbangkan perangkat secara keseluruhan, tetapi dengan semua detail menarik. Setidaknya itu yang kita tahu.

GPU itu sendiri adalah perangkat keras yang agak kuno dan usang. Ini terbatas pada 2048 poligon dan / atau 6144 simpul per bingkai. Resolusi adalah 256x192. Bahkan jika Anda melipatgandakan ini, kinerja tidak akan menjadi masalah. Dalam kondisi yang optimal, DS dapat menghasilkan hingga 1.22880 poligon per detik, yang konyol menurut standar GPU modern.

Sekarang mari kita beralih ke detail GPU. Di permukaan, ini terlihat cukup standar, tetapi jauh di dalam pekerjaannya sangat berbeda dari karya GPU modern, yang membuat persaingan beberapa fungsi lebih rumit.

GPU dibagi menjadi dua bagian: mesin geometri dan mesin rendering. Mesin geometri memproses simpul yang dihasilkan, membangun poligon, dan mengubahnya sehingga Anda dapat meneruskannya ke mesin rendering, yang (Anda tebak) menarik semua yang ada di layar.

Mesin geometri


Konveyor geometrik standar cantik.

Perlu disebutkan bahwa semua aritmatika dilakukan dalam bilangan tetap, karena DS tidak mendukung angka titik-mengambang.

Mesin geometri sepenuhnya ditiru secara pemrograman (GPU3D.cpp), yaitu, itu tidak berlaku banyak untuk apa yang kita gunakan untuk membuat grafik, tetapi saya akan tetap memberi tahu Anda lebih banyak tentang hal itu.

1. Transformasi dan pencahayaan. Vertikal dan koordinat tekstur yang dihasilkan dikonversi menggunakan set matriks 4x4. Selain warna titik, pencahayaan diterapkan. Semuanya cukup standar di sini, satu-satunya non-standar adalah cara kerja koordinat tekstur (1.0 = satu DS texel). Perlu juga disebutkan seluruh sistem tumpukan matriks, yang pada tingkat tertentu merupakan implementasi perangkat keras dari glPushMatrix ().

2. Mengkonfigurasi poligon. Vertikal yang dikonversi dirangkai menjadi poligon, yang bisa berupa segitiga, segi empat (paha depan), garis segitiga atau garis segi empat. Quads diproses secara asli dan tidak dikonversi menjadi segitiga, yang cukup bermasalah karena GPU modern hanya mendukung segitiga. Namun, sepertinya seseorang datang dengan solusi yang perlu saya uji.

3. Jatuhkan. Poligon dapat dibuang tergantung pada orientasi pada layar dan mode pemusnahan yang dipilih. Skema juga cukup standar. Namun, saya perlu mencari tahu bagaimana ini bekerja untuk paha depan.

4. Pemotongan. Poligon di luar cakupan visibilitas dihilangkan. Poligon yang sebagian membentang di luar wilayah ini terpotong. Langkah ini tidak membuat poligon baru, tetapi menambahkan simpul ke yang sudah ada. Faktanya, masing-masing dari 6 bidang pemotongan dapat menambahkan satu titik pada poligon, yaitu, sebagai hasilnya, kita bisa mendapatkan hingga 10 simpul. Di bagian mesin rendering, saya akan memberi tahu Anda bagaimana kami menangani hal ini.

5. Konversikan ke viewport. Koordinat X / Y dikonversi menjadi koordinat layar. Koordinat Z dikonversi agar sesuai dengan interval buffer kedalaman 24-bit.

Yang menarik adalah bagaimana koordinat W diproses: mereka "dinormalisasi" agar sesuai dalam interval 16-bit. Untuk ini, setiap koordinat W poligon diambil, dan jika lebih besar dari 0xFFFF, maka bergeser ke kanan dengan 4 posisi agar sesuai dalam 16 bit. Sebaliknya, jika koordinat kurang dari 0x1000, maka koordinatnya bergerak ke kiri hingga jatuh ke dalam interval. Saya kira ini perlu untuk mendapatkan interval yang baik, yang berarti akurasi yang lebih besar selama interpolasi.

6. Penyortiran. Poligon diurutkan sehingga poligon yang tembus pandang digambar terlebih dahulu. Kemudian mereka diurutkan berdasarkan koordinat Y mereka (ya), yang diperlukan untuk poligon buram dan opsional.

Selain itu, ini adalah alasan pembatasan 2048 poligon: untuk penyortiran, mereka perlu disimpan di suatu tempat. Ada dua bank memori internal yang dialokasikan untuk menyimpan poligon dan simpul. Bahkan ada register yang melaporkan berapa banyak poligon dan simpul yang disimpan.

Mesin rendering


Dan di sini kesenangan dimulai!

Setelah semua poligon telah dikonfigurasi dan disortir, mesin rendering mulai berfungsi.

Hal lucu pertama adalah bagaimana mengisi poligon. Ini sama sekali tidak seperti karya GPU modern yang melakukan pengisian genteng dan menggunakan algoritma yang dioptimalkan segitiga. Saya tidak tahu bagaimana mereka semua bekerja, tetapi saya melihat bagaimana hal ini dilakukan dalam GPU konsol 3DS, dan semuanya didasarkan pada ubin di sana.

Bagaimanapun, pada DS, rendering dilakukan dalam string raster. Para pengembang harus melakukan ini sehingga rendering dapat dilakukan secara paralel dengan mesin ubin dua dimensi jadul, yang melakukan menggambar pada garis raster. Ada buffer kecil dengan 48 garis raster yang dapat digunakan untuk menyesuaikan beberapa garis raster.

Rasterizer adalah renderer poligon cembung berdasarkan string raster. Ia dapat menangani sejumlah simpul yang berubah-ubah. Itu dapat membuat salah jika Anda melewati itu poligon yang tidak cembung atau memiliki tepi berpotongan, misalnya:


Poligon adalah kupu-kupu. Semuanya benar dan luar biasa.

Tetapi bagaimana jika kita memutarnya?


Aduh

Apa kesalahannya di sini? Mari kita menggambar garis besar poligon asli untuk mencari tahu:


Penyaji hanya dapat mengisi satu celah per baris raster. Ini mendefinisikan tepi kiri dan kanan mulai dari puncak tertinggi, dan mengikuti tepi ini sampai bertemu puncak baru.

Pada gambar yang ditunjukkan di atas, ia mulai dari puncak paling atas, yaitu, kiri atas, dan terus mengisi sampai mencapai ujung tepi kiri (simpul kiri bawah). Dia tidak tahu bahwa ujung-ujungnya bersilangan.

Pada titik ini, ia mencari titik berikutnya di tepi kirinya. Sangat menarik untuk dicatat bahwa dia tahu bahwa dia tidak perlu mengambil simpul yang lebih tinggi dari yang ada sekarang, dan juga tahu bahwa ujung kiri dan kanan telah berganti. Karena itu, terus mengisi hingga akhir tempat pembuangan akhir.

Saya akan menambahkan beberapa contoh lagi poligon non-cembung, tetapi kita akan menyimpang terlalu jauh dari topik.

Mari kita lebih memahami bagaimana Gouraud shading dan texturing bekerja dengan sejumlah simpul. Ada algoritma barycentric yang digunakan untuk menginterpolasi data sepanjang segitiga, tapi ... dalam kasus kami, mereka tidak cocok.

Penyaji DS di sini juga memiliki implementasinya sendiri. Beberapa gambar lebih menarik.


Titik-titik poligon adalah poin 1, 2, 3, dan 4. Angka-angka tidak sesuai dengan urutan traversal nyata, tetapi Anda mengerti artinya.

Dalam garis raster saat ini, renderer mendefinisikan simpul langsung di sekitar tepi (seperti yang disebutkan di atas, itu mulai dari simpul paling atas, dan kemudian melewati tepi sampai mereka selesai). Dalam kasus kami, ini adalah simpul 1 dan 2 untuk tepi kiri, 3 dan 4 untuk tepi kanan.

Kemiringan tepi digunakan untuk menentukan batas celah, yaitu, poin 5 dan 6. Pada titik-titik ini, atribut dari simpul diinterpolasi berdasarkan posisi vertikal di tepi (atau posisi horizontal untuk tepi, lereng yang sebagian besar sepanjang sumbu X).

Kemudian, untuk setiap piksel dalam celah (misalnya, untuk titik 7), atribut berdasarkan posisi X di dalam celah diinterpolasi dari atribut yang sebelumnya dihitung pada poin 5 dan 6.

Di sini, semua koefisien yang digunakan sama dengan 50% untuk menyederhanakan pekerjaan, tetapi artinya jelas.

Saya tidak akan masuk ke detail interpolasi atribut, meskipun juga akan menarik untuk menulis tentang ini. Sebenarnya, ini adalah interpolasi yang benar dari sudut pandang perspektif, tetapi memiliki penyederhanaan dan fitur yang menarik.

Sekarang mari kita bicara tentang bagaimana DS mengisi poligon.

Aturan pengisian apa yang dia gunakan? Ada juga banyak hal menarik di sini!

Pertama, ada aturan pengisian yang berbeda untuk poligon buram dan tembus cahaya. Namun yang paling penting, aturan ini berlaku piksel demi piksel . Poligon tembus cahaya dapat memiliki piksel buram, dan mereka akan mengikuti aturan yang sama dengan poligon buram. Anda dapat menebak bahwa untuk meniru trik seperti itu pada GPU modern, diperlukan beberapa rendering pass.

Selain itu, atribut poligon yang berbeda dapat memengaruhi rendering dengan berbagai cara menarik. Selain warna dan kedalaman buffer yang cukup standar, renderer juga memiliki buffer atribut yang melacak segala macam hal menarik. Yaitu: ID poligon (terpisah untuk poligon buram dan tembus pandang), tembus piksel, kebutuhan untuk menerapkan kabut, apakah poligon ini diarahkan ke atau dari kamera (ya, ini juga), dan apakah pikselnya ada di tepi poligon. Dan mungkin sesuatu yang lain.

Tugas meniru sistem seperti itu tidak akan sepele. GPU modern biasa memiliki buffer stensil terbatas hingga 8 bit, yang jauh dari cukup untuk semua yang dapat menyimpan buffer atribut. Kita harus menemukan solusi yang sulit.

Mari kita cari tahu:

* Pembaruan buffer kedalaman: diperlukan untuk piksel buram, opsional untuk yang tembus cahaya.

* ID Poligon: ID 6-bit ditugaskan untuk poligon, yang dapat digunakan untuk beberapa tujuan. ID poligon buram digunakan untuk menandai tepi. ID poligon tembus dapat digunakan untuk mengontrol di mana mereka akan ditarik: piksel tembus tidak akan ditarik jika ID poligon sesuai dengan ID poligon transparan yang sudah ada dalam buffer atribut. Selain itu, kedua ID poligon juga digunakan untuk mengontrol rendering bayangan. Misalnya, Anda dapat membuat bayangan yang menutupi lantai, tetapi bukan karakternya.

(Catatan: bayangan hanyalah implementasi buffer stensil, tidak ada yang mengerikan di sini.)

Perlu dicatat bahwa saat membuat piksel tembus cahaya, ID yang ada dari poligon buram disimpan, serta bendera tepi poligon buram terakhir.

* Bendera kabut: menentukan apakah akan menerapkan pass kabut untuk piksel ini. Proses memperbarui tergantung pada apakah piksel yang masuk buram atau tembus cahaya.

* Bendera garis depan: di sini ada masalah dengannya. Lihatlah tangkapan layar:


Sands of Destruction, layar game ini adalah serangkaian trik. Mereka tidak hanya mengubah koordinat Y untuk memengaruhi penyortiran Y. Layar yang ditunjukkan pada screenshot ini mungkin yang terburuk.

Ini menggunakan kasus batas dari tes kedalaman: fungsi perbandingan "kurang dari" mengambil nilai yang sama jika game menggambar poligon memandang kamera melalui piksel buram poligon yang diarahkan menjauh dari kamera . Ya persis. Dan nilai Z dari semua poligon adalah nol. Jika Anda tidak meniru fitur ini, beberapa elemen akan hilang di layar.

Saya pikir ini dilakukan agar sisi depan objek selalu terlihat di sisi belakang, bahkan ketika mereka begitu datar sehingga nilai Z-nya sama. Dengan semua peretasan dan trik ini, penyaji DS mirip dengan versi peranti penangkap era DOS.

Bagaimanapun, meniru perilaku ini melalui GPU itu sulit. Tetapi ada kasus batas serupa pengujian kedalaman lainnya, yang juga perlu diuji dan didokumentasikan.

* rib flags: renderer melacak lokasi tepi poligon. Mereka digunakan dalam melewati terakhir, yaitu ketika menandai tepi dan anti-aliasing. Ada juga aturan khusus untuk mengisi poligon buram dengan anti-aliasing yang dinonaktifkan. Diagram di bawah ini menggambarkan aturan-aturan ini:


Catatan: gambar rangka diberikan dengan mengisi hanya bagian tepinya! Langkah yang sangat cerdas.

Catatan lain yang menyenangkan tentang buffering dalam:

Ada dua kemungkinan mode buffering kedalaman pada DS: Z-buffering dan W-buffering. Ini tampaknya cukup standar, tetapi hanya jika Anda tidak merinci.

* Z-buffering menggunakan koordinat Z yang dikonversi agar sesuai dengan interval buffer kedalaman 24-bit. Koordinat Z secara linear diinterpolasi lebih dari poligon (dengan beberapa keanehan, tetapi mereka tidak terlalu penting). Tidak ada yang non-standar di sini.

* Dalam W-buffering, koordinat W digunakan "apa adanya". GPU modern biasanya menggunakan 1 / W, tetapi DS hanya menggunakan aritmatika titik tetap, jadi menggunakan nilai resiprokal sangat tidak nyaman. Bagaimanapun, dalam mode ini, koordinat W diinterpolasi dengan koreksi perspektif.

Seperti apa tampilan render akhir:

* penandaan tepi: piksel yang memiliki flag tepi ditetapkan diberi warna yang diambil dari tabel dan ditentukan berdasarkan ID poligon buram.

Mereka akan berwarna tepi poligon. Perlu dicatat bahwa jika poligon yang tembus cahaya digambar di atas poligon yang buram, maka ujung-ujung poligon akan tetap berwarna.

Efek samping dari prinsip pemotongan: batas di mana poligon bersinggungan dengan batas layar juga akan diwarnai. Misalnya, Anda dapat memperhatikan ini di tangkapan layar Picross 3D.

* kabut: diterapkan untuk setiap piksel berdasarkan pada nilai kedalaman yang digunakan untuk mengindeks tabel kepadatan kabut. Seperti yang mungkin Anda tebak, ini berlaku untuk piksel yang memiliki tanda kabut yang diatur dalam buffer atribut.

* antialiasing (penghalusan): itu diterapkan ke tepi (buram) poligon. Berdasarkan pada kemiringan tepi saat merender poligon, nilai cakupan piksel dihitung. Pada pass terakhir, piksel ini dicampur dengan piksel di bawahnya menggunakan mekanisme rumit yang saya jelaskan di posting sebelumnya.

Antialiasing seharusnya tidak (dan tidak bisa) ditiru dengan cara ini pada GPU, jadi ini tidak penting di sini.

Kecuali jika tanda tepi dan anti-aliasing harus diterapkan pada piksel yang sama, mereka hanya mendapatkan ukuran tepi, tetapi dengan opacity 50%.

Saya tampaknya telah menggambarkan proses rendering lebih atau kurang dengan baik. Kami tidak mempelajari pencampuran tekstur (menggabungkan titik dan warna tekstur), tetapi dapat ditiru dalam shader fragmen. Hal yang sama berlaku untuk penandaan tepi dan kabut, asalkan kita menemukan jalan di seluruh sistem ini dengan buffer atribut.

Tetapi secara umum, saya ingin menyampaikan yang berikut: OpenGL atau Vulkan (serta Direct3D, atau Glide, atau apa pun) tidak akan membantu di sini. GPU modern kami memiliki kekuatan lebih dari cukup untuk bekerja dengan poligon mentah. Masalahnya adalah detail dan fitur rasterisasi. Dan ini bahkan bukan tentang idealitas piksel, misalnya, lihat saja pelacak isu emulator DeSmuME untuk memahami masalah apa yang dihadapi pengembang saat merender melalui OpenGL. Kami juga harus berurusan dengan masalah yang sama ini.

Saya juga mencatat bahwa menggunakan OpenGL akan memungkinkan kami untuk port emulator, misalnya, untuk Beralih (karena pengguna Github bernama Hydr8gon mulai membuat port untuk emulator kami pada Switch ).

Jadi ... semoga aku beruntung.

Source: https://habr.com/ru/post/id429160/


All Articles