Ultimamente sto lavorando ad un renderer OpenGL rinnovato per il mio nCine, ed una delle questioni aperte era il decidere un metodo efficiente per raggruppare le draw call degli sprite e ridurre il carico di lavoro per driver e GPU.
Chi meglio del nostro esimio Corralx poteva porre fine ad ogni dubbio? π
Le chat sono stato copiate dal nostro server Discord con il benestare dei partecipanti. π
La prima risale al 15 Febbraio 2018 e puΓ² essere ritrovata cercando from: encelo#1703 in: rendering during: 2018-02-15 consulenza
:
[9:43 AM] encelo: Una consulenza per Corrado, per il disegno di tanti sprite in un batch, dicevi senza instancing e instanced arrays... Se mancano gli SSBO, passare i dati delle istanze con UBO Γ¨ ragionevole?
[9:44 AM] encelo: Per evitare di duplicare attributi per ogni vertice
[10:20 AM] Corralx: uhm si a meno che non hai decine/centinaia di migliaia di elementi
[10:20 AM] Corralx: gli UBO hanno un limite di dimensione molto piu' basso degli SSBO
[10:20 AM] Corralx: devi controllare GL_MAX_UNIFORM_BLOCK_SIZE
[10:20 AM] Corralx: su desktop di solito e' 65k ma su mobile potrebbe essere piu' piccolo
[10:21 AM] Corralx: (non credo in pratica sia un problema)
[10:21 AM] KIKIJIKI: Su GCN mi pare fosse un po' una storia diversa
[10:22 AM] Corralx: su GCN te ne fotti perche' ha una heap sola in pratica
[10:22 AM] Corralx: UBO/SSBO/Texture sono la stessa cosa e usano lo stesso hardware e le stesse istruzioni lol(edited)
[10:22 AM] KIKIJIKI: Β―_(γ)_/Β―
[10:22 AM] Corralx: difatti nel profiler AMD le fetch per i buffer sono contate come Texture Load π
[10:23 AM] Corralx: Nvidia tipicamente ha 2 memorie e path per il fetch diversi
[10:24 AM] encelo: si, la grandezza minima garantita e' di 16kb ma quasi tutti offrono 64k
[10:25 AM] encelo: alla fine ne posso usare piu' di uno, chi se ne frega
[10:25 AM] encelo: che invece di 3 drawcall ne escono 4 chi se ne fotte π
[10:25 AM] Corralx: si se proprio ti limiti splitti le call
[10:26 AM] Corralx: qualche centinaio di sprite per call c'e' le metti, dipende quanti dati devi passare
[10:26 AM] encelo: poi, invece, con a disposizione compute e ssbo si puo' andare di mega ring buffer in persistent mapping con sincronizzazione
[10:26 AM] encelo: in stile Vulkan
[10:26 AM] encelo: pero' per GLES 3.0 e GL 3.3 va bene cosi'
[10:26 AM] Corralx: anche, su Nvidia persistent mapping e' la scelta migliore di solito
[10:27 AM] Corralx: su AMD Map Overwrite + Discard a inizio frame e' piu' veloce
[10:28 AM] Corralx: e visto che ci siamo, pillola del giorno: non fate Map Discard a ogni constant buffer update pl0x
[10:29 AM] Corralx: il driver piange e io con lui
La seconda, risalente al 12 Marzo 2018, Γ¨ possibile ritrovarla cercando: from: encelo#1703 in: rendering during: 2018-03-12 sapienza
.
[10:38 AM] encelo: @Corralx Corrado, invoco la tua sapienza... aggiornare degli UBO su GL33 o GLES3... meglio buffersubdata? meglio mapbufferrange?
[10:38 AM] encelo: e poi, e' meglio avere un set di UBOs per ogni frame, accumulare le uniform e poi al frame successivo scivere su un set di UBO diversi? in stile Vulkan con un triplo buffering
[10:40 AM] Corralx: Dipende dal driver purtroppo lol
[10:42 AM] KIKIJIKI: Attenti all'UBO
[10:44 AM] encelo: stavo leggendo questo: https://lists.freedesktop.org/archives/mesa-commit/2013-October/045881.html
[10:44 AM] encelo: in generale non dovrebbe sempre funzionare il triplo buffo in faccia?
[10:45 AM] Corralx: il path preferito e piu' veloce in generale e' il discard a inizio frame + suballocate con no overwrite
[10:45 AM] encelo: un solo grande VBO con tutti i vertici, un set di UBO per tutte le uniform, ed al prossimo frame si ruota tra tre set
[10:45 AM] Corralx: non ti serve il buffering con gli UBO
[10:46 AM] Corralx: a inizio frame chiami glMapBufferRange
con GL_MAP_INVALIDATE_BUFFER_BIT
(che corrisponde a MAP_DISCARD
per D3D)
[10:46 AM] Corralx: poi scrivi i tuoi uniform man mano che ti servono e chiami esplicitamente la flush del range che hai scritto
[10:47 AM] Karbb: ( che supercazzola )
[10:47 AM] encelo: flush range per range? e poi un unmap prima di disegnare
[10:48 AM] Corralx: cioe' mappi con GL_MAP_UNSYNCHRONIZED_BIT
e GL_MAP_FLUSH_EXPLICIT_BIT
, scrivi un pezzo del buffer, e poi fai la unmap + glFlushMappedBufferRange
[10:48 AM] encelo: e poi il set di UBOs non si tocca fino a fine frame, al prossimo si riciclano
[10:48 AM] Corralx: che corrisponde a NO_OVERWRITE
in D3D
[10:48 AM] Corralx: quindi dici al driver che tu hai scritto solo pezzi del buffer che la GPU non stava usando ed e' safe copiarli in GPU memory senza sincronizzare
[10:49 AM] Corralx: quando il buffer e' full o ad inizio frame rifai la Discard e riparti con offset 0
[10:49 AM] encelo: che sarebbe l'alternativa piu' elaborata ad avere il buffering triplo
[10:49 AM] Corralx: si diciamo che il buffering lo fa il driver per te
[10:49 AM] encelo: perche' devo vedere se tutte queste cose belle sono in GLES 3.0
[10:49 AM] Corralx: e' quello che faresti in Vulkan anche che non ha discard mapping
[10:50 AM] Corralx: yep sono GL3 e GLES3
[10:50 AM] encelo: eh, in Vulkan il driver si fida e basta π
[10:50 AM] Corralx: questo e' il fast path consigliato di solito comunque per uniform/constant data
[10:51 AM] Corralx: discard a inizio frame di un grande buffer, suballocate con no overwrite e flushing esplicito del range per uploadare in GPU memory(edited)
[10:51 AM] encelo: poi dietro le quinte c'e' un buffering implicito dicevi, no?
[10:51 AM] encelo: quindi non dai fastidio alla GPU, perche' ti da un buffer nuovo e tu inizia a scrivere senza stalli
[10:51 AM] Corralx: si il driver usa una pool di buffer e fa il buffering per te
[10:51 AM] encelo: ok π
[10:52 AM] Corralx: quando fai discard o ne ha uno disponibile perche' la GPU ha finito di usarlo, oppure ne alloca uno nuovo
[10:52 AM] encelo: quindi posso dire addio a buffersubdata per sempre?
[10:52 AM] Corralx: buffersubdata si usa comunque per altri dati, tipo SSBO e TBO
[10:53 AM] encelo: e per i VBO?
[10:53 AM] Corralx: anche, tutta roba che non dovrebbe cambiare N mila volta per frame
[10:53 AM] encelo: stessa tecnica degli UBO, intendi?
[10:54 AM] Corralx: no VBO di solito si usa buffersubdata
[10:55 AM] encelo: quindi se volessi accorpare tutti i vertici a inizio frame?
[10:55 AM] Corralx: hai upload di vertici dinamico?
[10:56 AM] encelo: cambio i vertici una volta, poi rimangono fissi durante il frame
[10:57 AM] encelo: principalmente si tratta di testo, quindi di glifi
[10:57 AM] encelo: visto che per gli sprite sono passato al generare posizioni e uv dentro al vertex shader
[10:57 AM] Corralx: Ah, in quel caso puoi scriverli a inizio frame e fare glBufferSubData
[10:58 AM] Corralx: pero' dipende quanti dati sono anche in generale, ogni driver ha i suoi fast path
[10:58 AM] Corralx: su Nvidia probabilmente mappare con GL_MAP_PERSISTENT_BIT
e chiamare la flush a mano e' piu' efficiente
[10:58 AM] encelo: mi basta non fare palesi errori, piu' che essere il piu' meglio sempre π
[10:58 AM] encelo: non c'e' la persistenza sulle versioni che uso
[10:59 AM] Corralx: io userei discard + no overwrite per roba che ha frequenza frame+, buffersubdata per il resto
[10:59 AM] encelo: l'idea sarebbe OpenGL 3.3 e GLES 3.0 e poi in futuro magari Vulkan, piuttosto che supportare OpenGL piu' moderne
[11:00 AM] encelo: ma pure gli UBO non li cambio piu', come i vertici π
[11:00 AM] encelo: accumulo tutti i dati a inizio frame
[11:00 AM] encelo: perche' non buffersubdata in quel caso?
[11:00 AM] KIKIJIKI: Ti consiglio di passare a Vulkan
[11:01 AM] encelo: aveva ragione TextureMind π©
[11:01 AM] Corralx: gli UBO li cambi ogni frame no?
[11:01 AM] encelo: ed anche i VBO con il testo, potenzialmente
[11:01 AM] Corralx: in generale immagino tu voglia cambiarli ogni call
[11:01 AM] encelo: eh no, se alteri gli UBO ad ogni draw call il driver Intel piange π
[11:01 AM] Corralx: > driver > Intel
[11:02 AM] encelo:
See if we can unsynchronized write the data into the user's BO.
This avoids GPU stalls in unfortunately common user patterns (uploading sequentially into a BO, with draw calls in between each upload).
[11:02 AM] encelo: che sarebbe questo commit: https://lists.freedesktop.org/archives/mesa-commit/2013-October/045881.html
[11:02 AM] Corralx: eh perche' devi mappare con l'unsichronized bit + explicit flush
[11:03 AM] Corralx: altrimenti il driver sincronizza per forza
[11:03 AM] encelo: visto che cercavo il messaggio di warning che il driver mi sputava fuori
[11:03 AM] Corralx: perche' gli stai scrivendo su un buffer in uso dalla GPU
[11:03 AM] Corralx: cioe' questo e' un workaround per fixare applicazioni scritte da dei cani π
[11:03 AM] encelo: π
[11:04 AM] Corralx: comunque se sai gia' ad inizio frame tutti gli uniform che ti servono puoi usare buffersubdata con triple buffering
[11:04 AM] Corralx: (ma anche buffering variabile, ho visto stalli con triple buffering in alcuni casi estremi)(edited)
[11:05 AM] Corralx: se fai update + render sullo stesso thread triple buffering dovrebbe essere sufficiente
[11:05 AM] encelo: yeap
[11:06 AM] Corralx: in generale discard + no overwrite per update frequenti, buffersubdata per robe meno frequenti e piu' massicce in termini di quantita' di dati
[11:06 AM] Corralx: pero' su Nvidia Linux buffersubdata di solito e' piu' veloce quindi sticazzi π
[11:06 AM] Corralx: noi usiamo un path diverso per ogni combinazioni di flag + driver + frequenza di update
[11:07 AM] Corralx: se vedessi il nostro codice per l'upload di un buffer ti verrebbe male
[11:07 AM] encelo: eh, immagino
[11:07 AM] Corralx: la quantita' di path per ogni caso mi fa piangere ogni volta
[11:07 AM] encelo: che e' il tipo di merda che vorrei evitare
[11:07 AM] Corralx: per fortuna lo abbiamo quasi abbandonato per Vulkan e Metal
La terza Γ¨ del 3 Maggio 2018 e puΓ² essere ritrovata cercando from: encelo#1703 in: rendering during: 2018-05-03 consulenza
:
[1:29 PM] encelo: Consulenza per @Corralx, sempre per il batching, ma questa volta di mesh arbitrarie. Condividono la stessa texture, shader e tipo di primitiva, ma non il numero di vertici, come accedere ai dati delle singole istanze?
[1:30 PM] encelo: Ricordi del trucco per gli sprite di fare la divisione per il numero di vertici, costante, per ricavare l'indice dell'array di strutture nell'UBO?
[1:31 PM] encelo: Come fare nel caso di mesh arbitrarie? Senza multidraw e roba troppo avanzata, deve funzionare su OpenGL 3.3
[1:33 PM] Corralx: non lo fai lol
[1:33 PM] Corralx: sono statici sti vertici?
[1:36 PM] Corralx: se lo sono puoi precomputare l'index buffer in maniera che ogni indice sia una combinazione bitwise di draw_id + vertex_id
[1:37 PM] Corralx: tipo i 16 bit piu' significativi l'indice della draw, i 16 meno significativi l'indice del vertice
[1:37 PM] encelo: Ah, bel pezzotto
[1:37 PM] Corralx: e poi fai vertex pulling leggendo dall'index buffer
[1:38 PM] encelo: Oppure crei un vertex attribute con il drawId, ma bella merda π
[1:38 PM] encelo: Impostare un indice per ogni vertice, sozzissimo
[1:38 PM] Corralx: nah di solito si costruiscono index buffer custom per ste cose
[1:39 PM] Corralx: pero' che ti convenga rispetto a fare le draw in maniera tradizionale e' tutto da vedere
[1:39 PM] encelo: Eh, appunto
[1:39 PM] Corralx: quella e' la soluzione che si usa quando fai GPU driven rendering senza multidraw
[1:40 PM] encelo: Ma il batching tradizionale come si fa allora?
[1:40 PM] Corralx: cioe'?
[1:40 PM] encelo: Di raccogliere i vertici di piΓΉ mesh diverse e poi fare una chiamata
[1:40 PM] Corralx: se sono mesh diverse non puoi farlo
[1:41 PM] Corralx: usi un vertex buffer unico per tutta la roba statica per convenienza, ma poi fai le draw indipendenti ad ogni offset
[1:41 PM] encelo: Quindi urge multidraw
[1:42 PM] Corralx: comunque glMultiDrawArrays
e' core da OpenGL 1.4
[1:43 PM] Corralx: http://docs.gl/gl2/glMultiDrawArrays
[1:43 PM] Corralx: multidraw indirect e' 4.3
[1:44 PM] Corralx: tutti i dati devono venire dallo stesso VAO/VB/IB chiaramente, ma funziona
[1:44 PM] Corralx: non esiste su OpenGL ES apparentemente pero'
[1:49 PM] encelo: ma senza gl_DrawID pero', che viene introdotto con: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_draw_parameters.txt
[1:55 PM] Corralx: ah vero
[1:56 PM] Corralx: multidraw Γ© inutile in opengl
[1:57 PM] encelo: https://www.g-truc.net/post-0518.html
[1:57 PM] encelo: mi ricordo di questa presentazione di una collega in ARM, sul batching: https://community.arm.com/graphics/b/blog/posts/game-set-and-batch
[1:58 PM] encelo: ed in pratica si usa il pezzotto del vertex attribute che memorizza l'indice della mesh
[1:58 PM] encelo: ovviamente con una mesh da mille vertici hai mille ripetizioni dell'indice, e cosi' via π
[2:00 PM] encelo: meglio di niente
[2:00 PM] encelo: cioe', forse... bisogna misurare le performance π
[2:08 PM] encelo: su Vulkan immagino che stocazzo, registri i vari comandi separatamente nel buffer e bon
[2:12 PM] Corralx: si non ti serve il multidraw su Vulkan