Sinkronisasi antara utas logika game dan utas rendering


16

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?


1
Saya benci hanya mengirim spam tautan, tetapi saya pikir ini adalah bacaan yang sangat bagus dan harus menjawab semua pertanyaan Anda: altdevblogaday.com/2011/07/03/threading-and-your-game-loop
Roy T.


1
Tautan tersebut memberikan hasil akhir yang khas yang diinginkan seseorang, tetapi tidak merinci cara melakukannya. Apakah Anda akan menyalin seluruh adegan grafik setiap frame atau sesuatu yang lain? Diskusi terlalu tinggi dan tidak jelas.
user782220

Saya pikir tautannya cukup eksplisit tentang seberapa banyak status yang disalin dalam setiap kasus. misalnya. (dari tautan 1) "Batch berisi semua informasi yang diperlukan untuk menggambar bingkai, tetapi tidak mengandung status permainan lainnya." atau (dari tautan ke-2) "Namun, data masih perlu dibagikan, tetapi sekarang alih-alih setiap sistem mengakses lokasi data umum untuk mengatakan, dapatkan data posisi atau orientasi, setiap sistem memiliki salinannya sendiri" (Lihat khususnya 3.2.2 - Keadaan Manager)
DMGregory

Siapa pun yang menulis bahwa artikel Intel tampaknya tidak tahu bahwa threading tingkat atas adalah ide yang sangat buruk. Tidak ada yang melakukan sesuatu yang bodoh. Tiba-tiba seluruh aplikasi harus berkomunikasi melalui saluran khusus dan ada kunci dan / atau pertukaran negara besar yang terkoordinasi di mana-mana. Belum lagi tidak ada yang tahu kapan data yang dikirim akan diproses sehingga sangat sulit untuk berpikir tentang apa yang dilakukan kode. Jauh lebih mudah untuk menyalin data adegan yang relevan (tidak dapat diubah sebagai pointer yang dihitung ulang, bisa berubah - berdasarkan nilai) pada satu titik dan membiarkan subsistem mengatasinya seperti yang diinginkan.
snake5

Jawaban:


1

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.


Masalah yang akhirnya akan Anda hadapi adalah pengaturan ini tidak menskala dengan baik pada mesin multi-core. Ya, ada aspek-aspek permainan yang umumnya paling baik dilayani di utas mereka sendiri seperti audio, tetapi sebagian besar sisa gim tersebut sebenarnya dapat dikelola secara seri bersamaan dengan tugas-tugas penggabungan benang. Jika kumpulan thread Anda mendukung masker afinitas, Anda dapat dengan mudah mengantri mengatakan tugas render untuk dieksekusi pada utas yang sama dan meminta penjadwal thread mengelola antrean kerja utas dan melakukan pencuri kerja sesuai kebutuhan memberi Anda dukungan multi-threading dan multi-core.
Naros

1

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.


0

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:

synchronized(a){
    //code using a
}

C ++:

mutex a_mutex;

void f(){
    a_mutex.lock();
    //code using a
    a_mutex.unlock();
}

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.


1
Mutex belum tentu jawabannya di sini karena OP tidak hanya perlu memisahkan logika dan rendering game, tetapi mereka juga ingin menghindari kemacetan kemampuan satu utas untuk bergerak maju dalam pemrosesan itu terlepas dari di mana benang lain saat ini mungkin dalam pemrosesan itu. lingkaran.
Naros

0

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.


0

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.