OpenSceneGraph: Grafik Adegan dan Pointer Cerdas

gambar

Pendahuluan


Dalam artikel sebelumnya, kami melihat kumpulan OpenSceneGraph dari sumber dan menulis contoh dasar di mana sebuah pesawat abu-abu menggantung di dunia ungu yang kosong. Saya setuju, tidak terlalu mengesankan. Namun, seperti yang saya katakan sebelumnya, dalam contoh kecil ini, ada konsep utama yang mendasari mesin grafis ini. Mari kita pertimbangkan secara lebih detail. Materi di bawah ini menggunakan ilustrasi dari blog Alexander Bobkov tentang OSG (Sayang sekali penulis meninggalkan tulisan tentang OSG ...). Artikel ini juga didasarkan pada bahan dan contoh-contoh dari buku OpenSceneGraph 3.0. Panduan pemula

Saya harus mengatakan bahwa publikasi sebelumnya menjadi sasaran kritik, yang saya setujui sebagian - materi keluar tanpa diucapkan dan dikeluarkan dari konteks. Saya akan mencoba memperbaiki kelalaian ini di bawah potongan.

1. Secara singkat tentang grafik adegan dan simpulnya


Konsep sentral dari mesin adalah apa yang disebut grafik adegan (bukan kebetulan bahwa ia terjebak dalam nama kerangka itu sendiri) - struktur pohon hierarkis yang memungkinkan Anda untuk mengatur representasi logis dan spasial dari adegan tiga dimensi. Grafik adegan berisi simpul akar dan simpul atau simpul tengah dan terminalnya yang terkait.

Sebagai contoh



Grafik ini menggambarkan adegan yang terdiri dari rumah dan meja di dalamnya. Rumah memiliki representasi geometris tertentu dan terletak di ruang tertentu dengan cara relatif terhadap sistem koordinat dasar tertentu yang terkait dengan simpul akar (root). Tabel ini juga dijelaskan oleh beberapa geometri, yang terletak dalam beberapa cara relatif terhadap rumah, dan bersama-sama dengan rumah - relatif terhadap simpul root. Semua node, memiliki properti umum, karena mereka mewarisi dari satu osg :: kelas Node, dibagi menjadi beberapa tipe sesuai dengan tujuan fungsionalnya

  1. Node grup (osg :: Grup) - adalah kelas dasar untuk semua node perantara dan dirancang untuk menggabungkan node lain ke dalam grup
  2. Node transformasi (osg :: Transform dan turunannya) - dirancang untuk menggambarkan transformasi koordinat objek
  3. Node geometris (osg :: Geode) - node terminal (daun) dari grafik adegan yang berisi informasi tentang satu atau lebih objek geometris.

Geometri objek pemandangan dalam OSG dijelaskan dalam sistem koordinat lokal objek itu sendiri. Node transformasi yang terletak di antara objek ini dan simpul akar mengimplementasikan transformasi koordinat matriks untuk mendapatkan posisi objek dalam sistem koordinat dasar.

Node melakukan banyak fungsi penting, khususnya, menyimpan keadaan tampilan objek, dan keadaan ini hanya mempengaruhi subgraph yang terkait dengan node ini. Beberapa panggilan balik dapat dikaitkan dengan node dalam grafik adegan, penangan kejadian yang memungkinkan Anda untuk mengubah keadaan node dan subgraf yang terkait dengannya.

Semua operasi global pada grafik adegan yang terkait dengan memperoleh hasil akhir pada layar dilakukan secara otomatis oleh mesin, dengan secara berkala melintasi grafik secara mendalam.

Dalam contoh yang diperiksa terakhir kali , adegan kami terdiri dari satu objek - model pesawat yang dimuat dari file. Melihat jauh ke depan, saya akan mengatakan bahwa model ini adalah simpul daun dari grafik adegan. Dilas dengan erat ke sistem koordinat basis global engine.

2. manajemen memori OSG


Karena node dari grafik adegan menyimpan banyak data tentang objek adegan dan operasi padanya, maka perlu untuk mengalokasikan memori, termasuk secara dinamis, untuk menyimpan data ini. Dalam hal ini, ketika memanipulasi grafik adegan, dan, misalnya, menghapus beberapa node, Anda perlu memonitor dengan hati-hati bahwa node yang dihapus dari grafik tidak lagi diproses. Proses ini selalu disertai dengan kesalahan, debugging yang memakan waktu, karena cukup sulit bagi pengembang untuk melacak pointer ke objek yang merujuk ke data yang ada dan yang harus dihapus. Tanpa manajemen memori yang efektif, kesalahan segmentasi dan kebocoran memori lebih mungkin terjadi.

Manajemen memori adalah tugas penting dalam OSG dan konsepnya didasarkan pada dua poin:

  1. Alokasi memori: memastikan alokasi jumlah memori yang diperlukan untuk menyimpan suatu objek.
  2. Kosongkan memori: Kembalikan memori yang dialokasikan ke sistem ketika tidak diperlukan.

Banyak bahasa pemrograman modern, seperti C #, Java, Visual Basic .Net dan sejenisnya, menggunakan pengumpul sampah untuk membebaskan memori yang dialokasikan. Konsep bahasa C ++ tidak menyediakan untuk pendekatan seperti itu, namun, kita dapat menirunya dengan menggunakan apa yang disebut smart pointer.

Saat ini, C ++ memiliki smart pointer dalam arsenalnya, yang disebut “out of the box” (dan standar C ++ 17 telah berhasil menghilangkan bahasa dari beberapa jenis smart pointer yang usang), tetapi hal ini tidak selalu terjadi. Versi OSG resmi nomor 0,9 yang paling awal lahir pada tahun 2002, dan ada tiga tahun lagi sebelum rilis resmi pertama. Pada saat itu, standar C ++ belum menyediakan untuk pointer cerdas, dan bahkan jika Anda percaya satu penyimpangan sejarah , bahasa itu sendiri sedang mengalami masa-masa sulit. Jadi penampilan sepeda dalam bentuk smart pointer sendiri, yang diimplementasikan dalam OSG, sama sekali tidak mengejutkan. Mekanisme ini sangat terintegrasi ke dalam struktur mesin, sehingga memahami operasinya mutlak diperlukan sejak awal.

3. The osg :: ref_ptr <> dan osg :: kelas yang direferensikan


OSG menyediakan mekanisme penunjuk pintarnya sendiri berdasarkan kelas template osg :: ref_ptr <> untuk mengimplementasikan pengumpulan sampah otomatis. Untuk pengoperasian yang benar, OSG menyediakan osg lain :: kelas yang direferensikan untuk mengelola blok memori yang referensinya dihitung.

Kelas osg :: ref_ptr <> menyediakan beberapa operator dan metode.

  • get () adalah metode publik yang mengembalikan pointer mentah, misalnya, ketika menggunakan template osg :: Node sebagai argumen, metode ini akan mengembalikan osg :: Node *.
  • operator * () sebenarnya adalah operator dereference.
  • operator -> () dan operator = () - memungkinkan Anda untuk menggunakan osg :: ref_ptr <> sebagai pointer klasik saat mengakses metode dan properti objek yang dijelaskan oleh pointer ini.
  • operator == (), operator! = () dan operator! () - memungkinkan Anda untuk melakukan operasi perbandingan pada pointer pintar.
  • valid () adalah metode publik yang mengembalikan true jika pointer yang dikelola memiliki nilai yang benar (bukan NULL). Ekspresi some_ptr.valid () setara dengan ekspresi some_ptr! = NULL jika some_ptr adalah penunjuk pintar.
  • rilis () adalah metode publik, berguna ketika Anda ingin mengembalikan alamat yang dikelola dari suatu fungsi. Tentang itu akan dijelaskan lebih detail nanti.

Kelas osg :: Referenced adalah kelas dasar untuk semua elemen grafik adegan, seperti node, geometri, render state, dan objek lain yang ditempatkan di atas panggung. Dengan demikian, membuat simpul akar adegan, kami secara tidak langsung mewarisi semua fungsi yang disediakan oleh kelas osg :: Referenced. Karena itu, dalam program kami ada pengumuman

osg::ref_ptr<osg::Node> root; 

Osg :: Kelas yang direferensikan berisi penghitung bilangan bulat untuk referensi ke blok memori yang dialokasikan. Penghitung ini diinisialisasi ke nol di konstruktor kelas. Itu bertambah satu ketika objek osg :: ref_ptr <> dibuat. Penghitung ini berkurang segera setelah referensi ke objek yang dijelaskan oleh pointer ini dihapus. Suatu objek secara otomatis dihancurkan ketika pointer pintar berhenti untuk referensi itu.

Osg :: Kelas yang direferensikan memiliki tiga metode publik:

  • ref () adalah metode publik yang bertambah dengan 1 jumlah referensi.
  • unref () adalah metode publik, berkurang dengan 1 jumlah referensi.
  • referenceCount () adalah metode publik yang mengembalikan nilai saat ini dari penghitung referensi, yang berguna ketika men-debug kode.

Metode ini tersedia di semua kelas yang berasal dari osg :: Dirujuk. Namun, harus diingat bahwa kontrol manual penghitung tautan dapat menyebabkan konsekuensi yang tidak terduga, dan menggunakan ini Anda harus memahami dengan jelas apa yang Anda lakukan.

4. Bagaimana OSG mengumpulkan sampah dan mengapa diperlukan


Ada beberapa alasan mengapa smart pointer dan pengumpulan sampah harus digunakan:

  • Meminimalkan kesalahan kritis: penggunaan pointer pintar memungkinkan Anda untuk mengotomatisasi alokasi dan membebaskan memori. Tidak ada petunjuk mentah yang berbahaya.
  • Manajemen memori yang efektif: memori yang dialokasikan untuk objek dibebaskan segera, segera setelah objek menjadi tidak perlu, yang mengarah pada penggunaan sumber daya sistem secara ekonomis.
  • Fasilitasi debugging aplikasi: memiliki kemampuan untuk melacak dengan jelas jumlah tautan ke suatu objek, kami memiliki peluang untuk berbagai jenis optimisasi dan eksperimen.

Misalkan grafik adegan terdiri dari simpul akar dan beberapa tingkat simpul anak. Jika simpul root dan semua simpul anak dikelola menggunakan kelas osg :: ref_ptr <>, maka aplikasi hanya dapat melacak pointer ke simpul root. Menghapus simpul ini akan menghasilkan penghapusan otomatis semua node anak secara berurutan.



Pointer pintar dapat digunakan sebagai variabel lokal, variabel global, anggota kelas, dan secara otomatis mengurangi jumlah referensi ketika pointer pintar keluar dari ruang lingkup.

Pointer pintar sangat disarankan oleh pengembang OSG untuk digunakan dalam proyek, tetapi ada beberapa poin mendasar yang harus Anda perhatikan:

  • Contoh osg :: Dirujuk dan turunannya dapat dibuat secara eksklusif di heap. Mereka tidak dapat dibuat di stack sebagai variabel lokal, karena destruktor dari kelas-kelas ini dinyatakan sebagai protected. Sebagai contoh

 osg::ref_ptr<osg::Node> node = new osg::Node; //  osg::Node node; //  

  • Anda dapat membuat node adegan sementara menggunakan pointer C ++ biasa, namun pendekatan ini tidak aman. Lebih baik menggunakan pointer pintar untuk memastikan bahwa grafik adegan dikelola dengan benar.

 osg::Node *tmpNode = new osg::Node; //  ,  ... osg::ref_ptr<osg::Node> node = tmpNode; //         ! 

  • Dalam kasus apa pun Anda tidak boleh menggunakan adegan tautan siklik di pohon ketika simpul merujuk ke dirinya sendiri secara langsung atau tidak langsung melalui beberapa level



Dalam contoh grafik grafik adegan, simpul Anak 1.1 merujuk ke dirinya sendiri, dan simpul Anak 2.2 juga merujuk ke simpul Anak 1.2. Jenis tautan semacam itu dapat menyebabkan perhitungan jumlah tautan yang salah dan perilaku program yang tidak terbatas.

5. Melacak objek yang dikelola


Untuk menggambarkan operasi mekanisme penunjuk pintar di OSG, kami menulis contoh sintetis berikut

main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/ref_ptr> #include <osg/Referenced> #include <iostream> #endif // MAIN_H 

main.cpp

 #include "main.h" class MonitoringTarget : public osg::Referenced { public: MonitoringTarget(int id) : _id(id) { std::cout << "Constructing target " << _id << std::endl; } protected: virtual ~MonitoringTarget() { std::cout << "Dsetroying target " << _id << std::endl; } int _id; }; int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(0); std::cout << "Referenced count before referring: " << target->referenceCount() << std::endl; osg::ref_ptr<MonitoringTarget> anotherTarget = target; std::cout << "Referenced count after referring: " << target->referenceCount() << std::endl; return 0; } 

Kami membuat osg :: kelas turunan direferensikan yang tidak melakukan apa pun kecuali dalam konstruktor dan destruktor yang melaporkan bahwa instance dibuat dan menampilkan pengidentifikasi yang ditentukan ketika instance dibuat. Buat instance kelas menggunakan mekanisme pointer pintar

 osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(0); 

Selanjutnya, kami menampilkan penghitung referensi untuk objek target

 std::cout << "Referenced count before referring: " << target->referenceCount() << std::endl; 

Setelah itu, buat smart pointer baru, berikan nilai dari pointer sebelumnya

 osg::ref_ptr<MonitoringTarget> anotherTarget = target; 

dan sekali lagi tampilkan penghitung referensi

 std::cout << "Referenced count after referring: " << target->referenceCount() << std::endl; 

Mari kita lihat apa yang kita dapatkan dengan menganalisis output dari program

 15:42:39:   Constructing target 0 Referenced count before referring: 1 Referenced count after referring: 2 Dsetroying target 0 15:42:42:   

Ketika konstruktor kelas dimulai, pesan yang sesuai ditampilkan, memberi tahu kami bahwa memori untuk objek dialokasikan dan konstruktor bekerja dengan baik. Selanjutnya, setelah membuat pointer pintar, kita melihat bahwa penghitung referensi untuk objek yang dibuat telah meningkat satu. Membuat pointer baru, menetapkan nilai dari pointer lama pada dasarnya membuat tautan baru ke objek yang sama, sehingga penghitung referensi bertambah oleh yang lain. Ketika program keluar, destruktor dari kelas MonitoringTarget dipanggil.



Mari kita lakukan percobaan lain dengan menambahkan kode tersebut ke akhir fungsi utama ()

 for (int i = 1; i < 5; i++) { osg::ref_ptr<MonitoringTarget> subTarget = new MonitoringTarget(i); } 

mengarah ke program "buang" seperti itu

 16:04:30:   Constructing target 0 Referenced count before referring: 1 Referenced count after referring: 2 Constructing target 1 Dsetroying target 1 Constructing target 2 Dsetroying target 2 Constructing target 3 Dsetroying target 3 Constructing target 4 Dsetroying target 4 Dsetroying target 0 16:04:32:   

Kami membuat beberapa objek di tubuh loop, menggunakan pointer pintar. Karena ruang lingkup pointer diperluas dalam hal ini hanya ke badan loop, ketika keluar, destruktor secara otomatis dipanggil. Ini tidak akan terjadi, cukup jelas, kami akan menggunakan pointer biasa.

Dengan membebaskan memori secara otomatis adalah fitur penting lainnya dari bekerja dengan pointer pintar. Karena osg :: destructor kelas turunan yang direferensikan dilindungi, kami tidak dapat secara eksplisit memanggil operator hapus untuk menghapus objek. Satu-satunya cara untuk menghapus objek adalah dengan mengatur ulang jumlah tautan ke sana. Tetapi kemudian kode kita menjadi tidak aman selama pemrosesan data multi-utas - kita dapat mengakses objek yang sudah dihapus dari utas lainnya.

Untungnya, OSG Memberikan solusi untuk masalah ini dengan penjadwal penghapusan objeknya. Penjadwal ini didasarkan pada penggunaan kelas osg :: DeleteHandler. Ia bekerja sedemikian rupa sehingga tidak melakukan operasi menghapus suatu objek dengan segera, tetapi melakukannya setelah beberapa saat. Semua objek yang akan dihapus disimpan sementara sampai saatnya tiba untuk penghapusan yang aman, dan kemudian semuanya dihapus sekaligus. Penjadwal penghapusan osg :: DeleteHandler dikendalikan oleh backend render OSG.

6. Kembali dari fungsi


Tambahkan fungsi berikut ke kode contoh kami

 MonitoringTarget *createMonitoringTarget(int id) { osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(id); return target.release(); } 

dan ganti panggilan ke operator baru di loop dengan panggilan ke fungsi ini

 for (int i = 1; i < 5; i++) { osg::ref_ptr<MonitoringTarget> subTarget = createMonitoringTarget(i); } 

Panggilan rilis () akan mengurangi jumlah referensi ke objek ke nol, tetapi bukannya menghapus memori, itu mengembalikan pointer aktual ke memori yang dialokasikan secara langsung. Jika pointer ini ditetapkan ke smart pointer lain, tidak akan ada kebocoran memori.

Kesimpulan


Konsep grafik adegan dan smart pointer adalah dasar untuk memahami prinsip operasi, dan karenanya penggunaan OpenSceneGraph yang efektif. Mengenai pointer cerdas OSG, ingatlah bahwa penggunaannya sangat penting ketika

  • Penyimpanan jangka panjang dari fasilitas diharapkan.
  • Satu objek menyimpan tautan ke objek lain
  • Anda harus mengembalikan pointer dari suatu fungsi

Kode sampel yang disediakan dalam artikel tersedia di sini .

Dilanjutkan ...

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


All Articles