Bagaimana cara memisahkan logika dan rendering game? Saya tahu sepertinya sudah ada beberapa pertanyaan di sini yang menanyakan hal itu tetapi jawabannya tidak memuaskan bagi saya.
Dari apa yang saya mengerti sejauh ini, titik memisahkan mereka ke utas yang berbeda adalah agar logika permainan dapat mulai berjalan untuk centang berikutnya segera daripada menunggu vsync berikutnya di mana rendering akhirnya kembali dari panggilan swapbuffer yang telah diblokir.
Tetapi secara khusus struktur data apa yang digunakan untuk mencegah kondisi balapan antara utas logika game dan utas rendering. Agaknya thread rendering memerlukan akses ke berbagai variabel untuk mencari tahu apa yang harus digambar, tetapi logika game bisa memperbarui variabel yang sama ini.
Apakah ada teknik standar de facto untuk menangani masalah ini. Mungkin seperti menyalin data yang diperlukan oleh utas render setelah setiap eksekusi logika game. Apa pun solusinya, apakah overhead sinkronisasi atau apa pun yang kurang dari hanya menjalankan semua utas?
sumber
Jawaban:
Saya telah mengerjakan hal yang sama. Kekhawatiran tambahan adalah bahwa OpenGL (dan setahu saya, OpenAL), dan sejumlah antarmuka perangkat keras lainnya, secara efektif menyatakan mesin yang tidak rukun dengan dipanggil oleh banyak utas. Saya tidak berpikir perilaku mereka bahkan didefinisikan, dan untuk LWJGL (mungkin juga JOGL) sering melempar pengecualian.
Apa yang akhirnya saya lakukan adalah membuat urutan utas yang mengimplementasikan antarmuka spesifik, dan memuatnya ke tumpukan objek kontrol. Ketika objek itu mendapat sinyal untuk mematikan game, itu akan berjalan melalui setiap utas, memanggil metode ceaseOperations () yang diimplementasikan, dan menunggu mereka untuk menutup sebelum menutup sendiri. Data universal yang mungkin relevan dengan rendering suara, grafik, atau data lainnya disimpan dalam urutan objek yang mudah menguap, atau tersedia secara universal untuk semua utas tetapi tidak pernah disimpan dalam memori utas. Ada sedikit penalti kinerja di sana, tetapi digunakan dengan benar, itu memungkinkan saya untuk secara fleksibel menetapkan audio ke satu utas, grafik ke yang lain, fisika ke yang lain, dan sebagainya tanpa mengikat mereka ke dalam "permainan loop" tradisional (dan menakutkan).
Jadi sebagai aturan, semua panggilan OpenGL melewati thread Graphics, semua OpenAL melalui thread Audio, semua input melalui thread Input, dan semua yang perlu dikhawatirkan oleh thread kontrol pengorganisasian adalah manajemen thread. Status permainan diadakan di kelas GameState, yang dapat mereka lihat sesuai kebutuhan. Jika saya pernah memutuskan bahwa, katakanlah, JOAL sudah berkencan dan saya ingin menggunakan edisi baru JavaSound sebagai gantinya, saya hanya menerapkan utas berbeda untuk Audio.
Semoga Anda melihat apa yang saya katakan, saya sudah memiliki beberapa ribu baris tentang proyek ini. Jika Anda ingin saya mencoba dan mengumpulkan sampel, saya akan melihat apa yang bisa saya lakukan.
sumber
Biasanya, logika yang berhubungan dengan render grafis melewati (dan jadwal mereka, dan kapan mereka akan berjalan, dll) ditangani oleh utas terpisah. Namun utas itu sudah diterapkan (naik dan turun) oleh platform yang Anda gunakan untuk mengembangkan loop game Anda (dan game).
Jadi untuk mendapatkan loop game di mana logika game memperbarui secara independen dari jadwal refresh grafis Anda tidak perlu membuat utas tambahan, Anda cukup memanfaatkan utas yang sudah ada untuk pembaruan grafis tersebut.
Ini tergantung pada platform apa yang Anda gunakan. Sebagai contoh:
jika Anda melakukannya di sebagian besar platform terkait Open GL ( GLUT untuk C / C ++ , JOLG untuk Java , OpenGL ES Android Action terkait ) mereka biasanya akan memberi Anda metode / fungsi yang secara berkala disebut oleh rendering thread, dan yang Anda dapat diintegrasikan ke dalam loop game Anda (tanpa membuat iterasi gameloop bergantung pada kapan metode itu dipanggil). Untuk GLUT menggunakan C, Anda melakukan sesuatu seperti ini:
glutDisplayFunc (myFunctionForGraphicsDrawing);
glutIdleFunc (myFunctionForUpdatingState);
dalam JavaScript, Anda dapat menggunakan Pekerja Web
karena tidak ada multi-threading (yang dapat Anda jangkau secara terprogram), Anda juga dapat menggunakan mekanisme "requestAnimationFrame" untuk mendapat pemberitahuan ketika rendering grafik baru akan dijadwalkan, dan lakukan pembaruan kondisi permainan sesuai .Pada dasarnya yang Anda inginkan adalah lingkaran permainan langkah campuran: Anda memiliki beberapa kode yang memperbarui keadaan permainan, dan yang disebut di dalam utas utama permainan Anda, dan Anda juga ingin secara berkala memasuki (atau dipanggil kembali oleh) yang sudah utas render grafik yang ada untuk kepala ketika kapan saatnya untuk menyegarkan grafik.
sumber
Di Jawa ada kata kunci "disinkronkan", yang mengunci variabel yang Anda berikan untuk membuatnya menjadi threadsafe. Dalam C ++ Anda dapat mencapai hal yang sama menggunakan Mutex. Misalnya:
Jawa:
C ++:
Mengunci variabel memastikan mereka tidak berubah saat menjalankan kode yang mengikutinya, jadi variabel tidak dapat diubah oleh utas pembaruan Anda saat Anda merendernya (sebenarnya mereka DO berubah, tetapi dari sudut pandang utas rendering Anda, mereka tidak t). Anda harus berhati-hati dengan kata kunci yang disinkronkan di Jawa, karena itu hanya memastikan pointer ke variabel / Objek tidak berubah. Atribut masih dapat berubah tanpa mengubah pointer. Untuk merenungkan hal ini, Anda dapat menyalin objek sendiri atau memanggil disinkronkan pada semua atribut objek yang tidak ingin Anda ubah.
sumber
Apa yang saya lihat secara umum menangani komunikasi logika / render thread adalah dengan melipatgandakan buffer data Anda. Dengan cara ini thread render mengatakan bucket 0 isinya dibaca. Logika thread menggunakan bucket 1 sebagai sumber input untuk frame berikutnya dan menulis data frame ke bucket 2.
Pada titik-titik sinkronisasi, indeks apa yang masing-masing dari ketiga bucket maksudnya ditukar sehingga data frame berikutnya diberikan ke thread render dan thread logika dapat melanjutkan ke depan.
Tetapi tidak perlu alasan untuk membagi rendering & logika menjadi utas masing-masing. Anda sebenarnya bisa menjaga serial game loop dan memisahkan frame rate render Anda dari langkah logika menggunakan interpolasi. Untuk memanfaatkan prosesor multi-inti menggunakan pengaturan semacam ini adalah di mana Anda akan memiliki kumpulan utas yang beroperasi pada kelompok tugas. Tugas-tugas ini dapat berupa hal-hal seperti daripada mengulangi daftar objek dari 0 hingga 100, Anda mengulangi daftar dalam 5 ember 20 di 5 utas secara efektif meningkatkan kinerja Anda tetapi tidak terlalu menyulitkan loop utama.
sumber
Ini adalah posting lama tetapi masih muncul jadi ingin menambahkan 2 sen saya di sini.
Pertama daftar data yang harus disimpan dalam UI / display thread vs thread thread. Di utas UI Anda dapat menyertakan 3d mesh, tekstur, info ringan, dan salinan data posisi / rotasi / arah.
Di utas logika permainan, Anda mungkin perlu ukuran objek gim dalam 3d, primitif pembatas (bola, kubus), data jala 3d disederhanakan (misalnya untuk tabrakan terperinci), semua atribut yang mempengaruhi gerakan / perilaku, seperti kecepatan objek, rasio putaran, dll., dan juga data posisi / rotasi / arah.
Jika Anda membandingkan dua daftar, Anda dapat melihat bahwa hanya salinan data posisi / rotasi / arah yang harus dilewati dari logika ke utas UI. Anda juga mungkin memerlukan beberapa jenis korelasi untuk menentukan objek permainan yang dimiliki data ini.
Bagaimana Anda melakukannya tergantung pada bahasa apa yang Anda gunakan. Di Scala Anda dapat menggunakan Memori Transaksional Perangkat Lunak, di Java / C ++ semacam penguncian / sinkronisasi. Saya suka data yang tidak dapat diubah sehingga saya cenderung untuk mengembalikan objek yang tidak dapat diubah baru untuk setiap pembaruan. Ini sedikit pemborosan memori tetapi dengan komputer modern itu bukan masalah besar. Tetap jika Anda ingin mengunci struktur data bersama Anda dapat melakukannya. Lihat kelas Exchanger di Jawa, menggunakan dua atau lebih buffer dapat mempercepat.
Sebelum Anda mulai berbagi data di antara utas, tentukan berapa banyak data yang sebenarnya perlu Anda lewati. Jika Anda memiliki octree mempartisi ruang 3d Anda, dan Anda dapat melihat 5 objek game dari total 10 objek, bahkan jika logika Anda perlu memperbarui semua 10, Anda hanya perlu menggambar ulang 5 yang Anda lihat. Untuk membaca lebih lanjut, periksa blog ini: http://gameprogrammingpatterns.com/game-loop.html Ini bukan tentang sinkronisasi tetapi ini menunjukkan bagaimana logika permainan dipisahkan dari tampilan dan tantangan apa yang perlu Anda atasi (FPS). Semoga ini membantu,
Menandai
sumber