Creare strutture OpenGL in un programma con multithreading?

Sto tentando di eseguire quanto segue in un motore fisico che sto costruendo:

Hanno 2 thread, uno per la logica mondiale, uno per il rendering.

Il thread principale (il thread da cui vengono creati gli altri thread) è il thread di rendering, quindi il thread del mondo viene biforcato da esso.

Nel thread di rendering esiste una struttura di dati globale denominata gestore di rendering dichiarata come:

class Renderer { private: /* shader stuff */ mutex busy_queue; vector render_queue; public: /* Bunch of methods */ void add_data(Render_Info*); void render(); }; 

E una struttura Render_Info è dichiarata come:

 struct Render_Info { mutex info_lock; GLuint VAO; vector VBOs; vector types; uint layouts; uint render_instances; Mesh* geometry; }; extern Renderer *Rendering_Handler; 

L’idea qui era la seguente. Qualsiasi thread che desideri rendere qualcosa, deve gestire i propri dati e inserirli in primitivi OpenGL. quindi inserisce tali informazioni in un object Render_Info , che funge da messaggio tra il thread e il thread di rendering.

Il thread usa quindi il metodo add_data() , per inviare un puntatore al suo messaggio di dati che viene aggiunto al render_queue come:

 void Renderer::add_data(Render_Info* data) { busy_queue.lock(); render_queue.push_back(data); busy_queue.unlock(); } 

Infine, quando il thread di rendering sceglie di eseguire il rendering di qualcosa, blocca la coda (impedendo che qualcosa venga aggiunto alla coda) esegue il rendering di tutto, quindi cancella la coda.

Ora ovviamente è necessaria una maggiore coordinazione delle discussioni, ma questo è il succo dell’idea.

Il problema è che ottengo errori di segmentazione solo provando a creare VAO e VBO OpenGL, per non parlare di riempirli di dati.

Da quello che ho letto OpenGL è il più lontano dall’essere infallibile come una giraffa dall’essere un delfino.

E la causa del problema sembra essere che il contesto OpenGL appartiene al thread principale, quindi quando provo a creare VAO e VBO sul thread mondiale OpenGL si blocca semplicemente perché non ha idea di cosa sta succedendo.

Che cosa posso fare allora fare il multi-thread del programma?

Mi piacerebbe rimanere il più vicino al design che ho descritto a meno che qualcuno non fornisca una buona giustificazione del perché non funzionerebbe.

Il requisito per OpenGL è che il contesto creato per il rendering debba essere posseduto da un singolo thread in un dato punto e il thread che possiede il contesto dovrebbe renderlo corrente e quindi chiamare qualsiasi funzione relativa a gl. Se lo fai senza possedere e rendendo attuale il contesto, ottieni errori di segmentazione. Di default il contesto sarà aggiornato per il thread principale. Quindi per rendere il tuo programma multi-thread hai due opzioni.

  1. Crea due contesti e condividi le risorse come gli oggetti di texture VAO tra di loro. Vantaggio di questo approccio è che puoi fare riferimento nel thread 2 a qualsiasi VAO creato nel thread 1 e non si bloccherà.

    Thread_1:

     glrc1=wglCreateContext(dc); glrc2=wglCreateContext(dc); BOOL error=wglShareLists(glrc1, glrc2); if(error == FALSE) { //Unable to share contexts so delete context and safe return } wglMakeCurrent(dc, glrc1); DoWork(); 

    Thread_2:

     wglMakeCurrent(dc, glrc2); DoWork(); 
  2. Un’altra opzione è creare un contesto per thread e renderlo corrente all’avvio del thread. Mi piace seguire

    Thread_1:

     wglMakeCurrent(NULL, NULL); WaitForThread2(); OrDoSomeCPUJob(); wglMakeCurrent(dc, glrc); 

    Thread_2:

     wglMakeCurrent(dc, glrc); DoSome_GL_Work(); wglMakeCurrent(NULL, NULL); 

Spero che questo chiarisca la cosa.

Da quello che ho letto OpenGL è il più lontano dall’essere infallibile come una giraffa dall’essere un delfino.

Allora sei disinformato. OpenGL è perfettamente thread-safe. Devi solo ricordare che i contesti OpenGL agiscono un po ‘come l’archiviazione locale dei thread. Vale a dire quando si rende corrente un contesto OpenGL, quindi questo è localizzato nel thread che effettua quella chiamata.

Anche i puntatori di funzione OpenGL estesi possono essere specifici per un contesto OpenGL (ma non per un binding di contesto). Tuttavia i caricatori di funzioni OpenGL conservano un thread → cache di contesto. Pertanto, quando si chiama una funzione OpenGL estesa (ad esempio una che deve essere caricata in fase di esecuzione) da una thread senza un contesto limitato, è probabile che si finisca per chiamare un puntatore a funzione non valido e ottenere un arresto anomalo.

Tuttavia, nonostante sia perfettamente thread-safe, OpenGL non guadagna necessariamente le prestazioni quando viene utilizzato multithread. I contesti zigote del thread OpenGL sono molto utili se è necessario aggiornare i dati dell’object texture e del buffer da un thread di lavoro, ma si deve fare attenzione a cose difficili che potrebbero essere utilizzate dal thread di rendering principale. Nei programmi in cui devo fare questo, il solito approccio è che il thread di generazione dei dati sta creando un pool di oggetti texture / buffer, aggiorna i dati al loro interno e quindi “consegna” la proprietà dell’object al thread di rendering. Alla fine il thread di rendering fa il suo gioco con questi oggetti e, una volta eseguito, passa la proprietà al thread di aggiornamento e prende l’object successivo dal proprio pool che viene riempito con ciò che il thread di dati invia.

Che cosa posso fare allora fare il multi-thread del programma?

Creare contesti OpenGL zigote e configurarli per condividere la loro trama e gli oggetti buffer con gli altri thread secondo il meccanismo di condivisione degli elenchi di visualizzazione. È ansible avere un numero arbitrario di contesti OpenGL nel programma e ogni thread può avere il proprio contesto molto attivo (mentre gli altri thread utilizzano contesti diversi).