Come memorizzare nella cache le risorse nel mio sistema di rendering homebrew


10

Sfondo:

Sto progettando un semplice sistema di rendering 3D per un'architettura di tipo sistema di componenti di entità usando C ++ e OpenGL. Il sistema è costituito da un renderer e un grafico di scena. Quando finisco la prima iterazione del renderer, potrei distribuire il grafico della scena nell'architettura ECS. Per ora è un segnaposto in un modo o nell'altro. Se possibile, i seguenti obiettivi sono per il renderer:

  1. Semplicità . Questo è per un progetto di ricerca e voglio essere in grado di cambiare ed espandere facilmente i miei sistemi (da qui l'approccio ECS).
  2. Prestazioni . La mia scena potrebbe avere molti piccoli modelli e anche grandi volumi con molta geometria. Non è accettabile acquisire oggetti dal contesto OGL e dalla geometria del buffer per ogni frame di rendering. Sto mirando alla localizzazione dei dati per evitare errori nella cache.
  3. Flessibilità . Deve essere in grado di eseguire il rendering di sprite, modelli e volumi (voxel).
  4. Disaccoppiato . Il grafico della scena può essere rifattorizzato nell'architettura ECS principale dopo aver scritto il mio renderer.
  5. Modulari . Sarebbe bello poter scambiare diversi renderer senza cambiare il mio grafico di scena.
  6. Trasparenza referenziale , nel senso che in qualsiasi momento posso dargli qualsiasi scena valida e renderò sempre la stessa immagine per quella scena. Questo obiettivo in particolare non è necessariamente richiesto. Ho pensato che avrebbe aiutato a semplificare la serializzazione delle scene (dovrò essere in grado di salvare e caricare scene) e offrirmi la flessibilità di scambiare scene diverse durante il runtime a scopo di test / sperimentazione.

Problema e idee:

Ho escogitato alcuni approcci diversi da provare, ma sto lottando su come memorizzare nella cache le risorse OGL (VAO, VBO, shader, ecc.) Per ciascun nodo di rendering. Di seguito sono riportati i diversi concetti di memorizzazione nella cache che ho pensato finora:

  1. Cache centralizzata. Ogni nodo scena ha un ID e il renderer ha una cache che mappa gli ID per renderizzare i nodi. Ogni nodo di rendering contiene VAO e VBO associati alla geometria. Una mancata cache acquisisce risorse e mappa la geometria su un nodo di rendering nella cache. Quando la geometria viene modificata, viene impostato un flag sporco. Se il renderer vede un flag di geometria sporco durante l'iterazione attraverso i nodi della scena, esegue il rebuffing dei dati utilizzando il nodo di rendering. Quando un nodo di scena viene rimosso, viene trasmesso un evento e il renderer rimuove il nodo di rendering associato dalla cache rilasciando risorse. In alternativa, il nodo è contrassegnato per la rimozione e il renderer è responsabile della sua rimozione. Penso che questo approccio raggiunga più da vicino l'obiettivo 6, considerando anche il 4 e 5. 2 soffre della complessità e perdita di localizzazione dei dati extra con le ricerche delle mappe invece dell'accesso alla matrice.
  2. Cache distribuita . Simile sopra ad eccezione di ogni nodo di scena ha un nodo di rendering. Questo ignora la ricerca della mappa. Per indirizzare la localizzazione dei dati, i nodi di rendering potrebbero essere memorizzati nel renderer. Quindi i nodi di scena potrebbero invece avere dei puntatori per renderizzare i nodi e il renderer imposta il puntatore su una cache mancata. Penso che questo tipo di imitazione di un approccio a componenti di entità, quindi sarebbe coerente con il resto dell'architettura. Il problema qui è che ora i nodi di scena contengono dati specifici dell'implementazione del renderer. Se cambio il modo in cui le cose vengono renderizzate nel renderer (come il rendering di sprite rispetto a volumi) ora devo cambiare il nodo di rendering o aggiungere più "componenti" al nodo della scena (il che significa cambiare anche il grafico della scena). Tra i lati positivi, questo sembra il modo più semplice per rendere operativo il mio renderer di prima iterazione.
  3. Metadati distribuiti . Un componente dei metadati della cache del renderer è archiviato in ciascun nodo di scena. Questi dati non sono specifici dell'implementazione ma contengono piuttosto un ID, un tipo e qualsiasi altro dato rilevante necessario alla cache. Quindi la ricerca nella cache può essere eseguita direttamente in un array usando l'ID e il tipo può indicare quale tipo di approccio di rendering usare (come sprite vs volumi).
  4. Visitatore + mappatura distribuita . Il renderer è un visitatore e i nodi scena sono elementi nel modello visitatore. Ogni nodo scena contiene una chiave cache (come i metadati ma solo un ID) che solo il renderer manipola. L'ID può essere utilizzato per l'array anziché per la ricerca di mappe generalizzate. Il renderer può consentire al nodo scena di inviare una diversa funzione di rendering in base al tipo di nodo scena e l'ID può essere utilizzato da qualsiasi cache. Un ID predefinito o fuori portata indica un errore nella cache.

Come risolveresti questo problema? O hai qualche suggerimento? Grazie per aver letto la mia bacheca di testo!


1
Hai fatto progressi?
Andreas,

Questa è una domanda estremamente complessa e probabilmente dovrebbe essere suddivisa in diverse domande separate. Questo essenzialmente sta chiedendo "Come dovrei progettare il mio motore?" Il mio consiglio sarebbe di progettare prima qualcosa che supporti i componenti più semplici, quindi aggiungere e refactoring le caratteristiche man mano che procedi. Inoltre, consulta i libri di progettazione del motore di gioco 3D, che dovrebbero coprire molte delle informazioni che stai cercando.
Ian Young,

Risposte:


2

Dopo aver riletto la tua domanda, sento fortemente che stai complicando troppo il problema. Ecco perché:

  1. Esistono solo due tipi di sistema di rendering: Forward e Deferred, nessuno dei quali dipende da un grafico di scena.

  2. Gli unici problemi di prestazioni che dovresti davvero ottenere con qualsiasi sistema di rendering, dovrebbero provenire da un elevato numero di poligoni, da shader e codice client inefficienti.

  3. I miss nella cache riducono davvero le prestazioni, ma non sono proprio i mostri che potresti pensare di essere. L'80% dei miglioramenti delle prestazioni proverrà da un algoritmo più efficiente. Non commettere l'errore di pre-ottimizzare il tuo codice.

Detto ciò:

Se stai usando uno scenegraph homebrew, allora dovresti già usare un'interfaccia "Renderer" (o classe base) per progettare la parte di rendering del tuo codice scenegraph. Il modello di visitatore che utilizza il doppio dispacciamento è un buon approccio a questo, poiché potresti benissimo usare molti tipi di nodi grafici come colore, trama, mesh, trasformazione ecc. In questo modo, durante il ciclo di rendering, tutto il renderer deve fare è percorri la struttura ad albero della scena in profondità, passando prima come argomento. In questo modo il renderer è fondamentalmente solo una raccolta di shader e forse un framebuffer o due. Il risultato di ciò è che il codice di ricerca / rimozione non è più necessario per il sistema di rendering, ma solo lo scenario stesso.

Esistono sicuramente altri modi per affrontare i problemi che affronti, ma non voglio dare una risposta troppo lenta. Quindi, il mio miglior consiglio è di far funzionare prima qualcosa di semplice, quindi ampliarlo per trovare i suoi punti deboli, quindi sperimentare altri approcci e vedere dove si trovano i loro punti di forza / debolezza.

Quindi sarai in una buona posizione per prendere una decisione informata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.