Skip to main content
GameDev.net gamedev.net
🔒 Locked

Time for a batched render?

Started by razor1911 Jan 17, 2011 at 2:12 PM 135 replies 27.4k views
Original Post
razor1911
razor1911
All done, engine is almost ready to go - and it is now the best time to optimize mesh rendering.

Average measures of 1000 frames:

fb10bb4b.jpg




My models are being drawn in non-batched approach, what means every model in the terrain cell is drawed separately with a call to drawelements.

Some models consist of two objects inside (trees) (separate VBO per object in model), some have flags for alpha blending on, for bump mapping - they're passed to the shaders everytime i call them to be rendered.

I have already made a piece of code in the class that 'sorts' the models by their model type for an render, so instead of rendering model types: 1,3,1,2,5,2,1,1,6,1

engine is rendering 1,1,1,1,1,2,2,3,5,6. This is the first thing that needs to be done, before i start to batch anything i suppose.




Now the only batching approach i came up to is as follows:

void class::renderCell()

{


//PSEUDOCODE

shaderON();

- X = how many objects make up this model

- Y = model's of that type in this cell

for X;X++

- bind vbo of the object

- set the shader uniform data

for Y=model_count_in_cell

pushmatrix

glTranslate to actual model.position

glRotate to actual model_rotation

draw_bound_vbo

popmatrix

endfor

- unbind vbo

endfor


-shaderOFF
}












Might be that i would have to 'consolidate' the VBO's of the models having the same type and render them with one call to get that frame timing better?

Before i do anything with that batching, i would like to ask you guys if the 'approach' i have suggested has some major 'cons' inside?




Thanks for any suggestions.
Juanxo
Juanxo
- culling on?
- i would start packing vbos with the same vertex format in the same vbo, and drawing the submeshes with glDrawRangeElements. (~10000 vertices per vbo seems to be good)
- Are you using indexed triangle list, or just triangle lists?
- it seems you sort by mesh type only in each cell, isn't it? It would be better to add all the models to be drawn each frame to a list/queue/vector first, sort this by mesh type and draw all

And, have you profiled all your code, not just the rendering? Try disabling draw calls or rasterization in GDEbugger and see if this gives you performance boost. If it doesn't, you aren't GPU bound and your bottlenecks are elsewhere
razor1911
razor1911
Juanxo, thanks for you suggestions.

- culling was off (christ sake!) - got +10fps.

applied culling to terrain rendering /grass and models.





- using nvtristrip



SetCacheSize(CACHESIZE_GEFORCE3);
SetListsOnly(true);
pObject->m_pIndices=OptimiseElements(pObject->m_pIndices,pObject->numIndices);








- sorting meshes by the model type per cell as you said. should i sort all meshes in all visible cells and render them instead of switching per cell?

- going to check the gdebugger now.







4111f1a9.jpg


Above, after CULLING ON + The approach i have given in the post.

Juanxo
Juanxo

- sorting meshes by the model type per cell as you said. should i sort all meshes in all visible cells and render them instead of switching per cell?



i think so, because altought you have to traverse the list twice (once to create all render items from models, and once to draw the render items), you will end with 1,1,1,2,2,2,3,3,4,5, for all your scene, and switch render states is slower than traverse some lists.

Also, check outTuts on organizing models before rendering? where the OP has similar questions
razor1911
razor1911
Juanxo, perfect - will have a look and keep this post updated.




Thanks mate.

razor1911
razor1911
An update regarding debugger,




- RASTER OPERATIONS OFF : no significant change

- DRAW CALLS OFF : around 2ms faster in Frame Time

karwosts
karwosts
One thing you may want to look at if you're shooting for performance is gdebugger's redundant state changes window. May help you to find some places to cut out some opengl calls, or show you where some local state caching may be a good idea.
[size=2]My Projects:
[size=2]Portfolio Map for Android - Free Visual Portfolio Tracker
[size=2]Electron Flux for Android - Free Puzzle/Logic Game
razor1911
razor1911
With gde - there's hell of a lot things to keep an eye on, but there are also things you cannot skip.

A dump after few things i finally optimized - mainly with batching the models render per cell.

Still not satisfied with the results but at least managed to get ~15fps up with the batch.




fccfa539.jpg

e6aff278.jpg



Ps. what is the 'good' CPU average for an engine running without VSYNC ?
mightypigeon
mightypigeon
The state changes are definitely killing you. You need to be able to batch on as many state changes as possible.

How many objects and polys are you rendering on average?

For static geometry are you able to pretransform it on load time and upload it to the graphics card, so you don't have to call glTranslate/glRotate


Ps. what is the 'good' CPU average for an engine running without VSYNC ?


That's going to differ a lot from engine to engine.
[size="1"]
razor1911
razor1911
mightypigeon, state changes are the target now - i'm doing a lot of things now to clear them up, so be back as soon as remove at least 60% of them.




Static geometry - every model would need to have it's own translation done on vertexes at the beginning, then data packed to VBO per model on the map - am i correct?

What about uniforming rotation/translation (2x vec3) data to shader, wouldn't be that faster than calling glTranslate/glRotate ?
karwosts
karwosts
Any particular reason you're not just doing your own matrix management? All that glTranslate stuff is out of date/deprecated , if you're going to send the transform data you might as well send the matrices.
[size=2]My Projects:
[size=2]Portfolio Map for Android - Free Visual Portfolio Tracker
[size=2]Electron Flux for Android - Free Puzzle/Logic Game
razor1911
razor1911
First of all, thank you all for helping me out with that.

Update: taken down the redundant calls for clearcolor / lightfv - achieved +5fps




karwosts ,

what would be your choice in that situation to take out the translation/rotation functions totally out per models ?

No matrix management for models on my side,

do you suggest to calculate the matrices on the CPU every time the model translation/rotation changes - and pass the matrix to the shader per model each frame - instead of doing rotate/translate?




karwosts
karwosts

do you suggest to calculate the matrices on the CPU every time the model translation/rotation changes - and pass the matrix to the shader per model each frame - instead of doing rotate/translate?
[/quote]

This is the only option in modern opengl, you're supposed to handle your own matrices and upload them as mat4's to the shader. All the glMatrixmode/glTranslate/glRotate/glLoadIdentity stuff is gone and deprecated.

Of course you can still use it if you want to, but that's not the modern way of doing it.
[size=2]My Projects:
[size=2]Portfolio Map for Android - Free Visual Portfolio Tracker
[size=2]Electron Flux for Android - Free Puzzle/Logic Game
razor1911
razor1911


do you suggest to calculate the matrices on the CPU every time the model translation/rotation changes - and pass the matrix to the shader per model each frame - instead of doing rotate/translate?


This is the only option in modern opengl, you're supposed to handle your own matrices and upload them as mat4's to the shader. All the glMatrixmode/glTranslate/glRotate/glLoadIdentity stuff is gone and deprecated.

Of course you can still use it if you want to, but that's not the modern way of doing it.
[/quote]
using those "glMatrixmode/glTranslate/glRotate/glLoadIdentity" - is considered to lower the performance too ?





maxgpgpu
maxgpgpu
[font="Book Antiqua"]My opinion is, "yes" you should work hard to batch your objects together.

That's the way I designed my 3D engine, right from the start. My engine creates a new batch every time it adds an object that has a never-before-encountered "primitive type". In general, this forces "points", "lines" and "all objects composed of triangles" to be in separate batches. Of course, whenever an object is added, and the batch corresponding to that "primitive type" has insufficient room to hold the object, another batch is created.

Then, when it comes time to render each frame (or each window if multiple windows exist), ALL objects in each batch are rendered with a single call of[font="Courier New"] glDrawElements()[/font]. This is extremely fast, of course.

However, this scheme works because "everything else" in my design is compatible with this. For example, I chose to transform ALL vertices to world-coordinates in the CPU. This means a single "viewprojection matrix" in the GPU is sufficient to transform all vertices of all objects to screen coordinates. You could accomplish the same thing by having an 8-bit (or so) "matrix ID" field in each vertex so the vertex shader could access the appropriate transformation matrix from a uniform buffer object.

The choice to "do it my way" was not an easy choice. What I mean is, it was quite an intellectual (and benchmarking) effort to determine whether putting this relatively large burden on the CPU was justified. I might not have taken this risk, except I know I can fall back to a separate local-to-world (or local-to-world-to-view-to-projection) matrix in uniform buffer objects as I mentioned above.

One reason for transforming vertices to world-coordinates in the CPU is "collision detection". The CPU needs all objects in a single consistent "coordinate system" to perform collision detection (both coarse-grain and fine-grain). Since performing this all inside the GPU is still relatively "wacko" (as in "difficult" [as hell] to design and implement efficiently), I prefer to do this work outside the engine.

Also, understand that the CPU only needs to transform those objects that have changed since the previous frame. In games and most other applications, only a small minority of objects are rotated, translated, skewed or scaled on a given frame. Indeed the vast majority of objects in most applications are fixed "environment" (terrain, walls, floors, ceilings, furniture, etc). My engine marks each object "modified" when it is manipulated (rotate, translate, skew, scale), and then only those modified since the previous frame are transformed to world-coordinates and updated in the VBO (with[font="Courier New"] glBufferSubData() [/font]as I recall).

The process of "pre-display culling" is also much more problematic in my engine, even to the point where culling probably doesn't make sense. Why? Because my engine supports multiple [moving] cameras in multiple positions looking in multiple directions with different zoom lenses rendering into multiple windows or FBOs (the FBOs contain textures which are often attached to mirrors and "virtual video displays" IN the game/application, any number of which may be visible in each window!). Try to figure out which objects can be culled in this scenario! It's more work than simply transforming everything... especially since everything needs to be transformed anyway to perform collision detection.

Anyway, all things considered (which is the difficulty to consider conceptually), my analysis says this "huge batch" approach is best. Of course, I also went to the effort to write my vertex transformation routines in SIMD assembly language (more than 2x faster), but what the hey! I also plan to "offload" this transformation work onto another CPU core --- assuming the executing CPU has another core, which is increasingly certain these days.

If you have other worries about this "super batch renderer" approach, mention them here and I'll say what is my take on them.
[/font]
karwosts
karwosts

[quote name='karwosts' timestamp='1295341993' post='4760620']

do you suggest to calculate the matrices on the CPU every time the model translation/rotation changes - and pass the matrix to the shader per model each frame - instead of doing rotate/translate?


This is the only option in modern opengl, you're supposed to handle your own matrices and upload them as mat4's to the shader. All the glMatrixmode/glTranslate/glRotate/glLoadIdentity stuff is gone and deprecated.

Of course you can still use it if you want to, but that's not the modern way of doing it.
[/quote]
using those "glMatrixmode/glTranslate/glRotate/glLoadIdentity" - is considered to lower the performance too ?

[/quote]

I would probably not expect it to have a performance difference.
[size=2]My Projects:
[size=2]Portfolio Map for Android - Free Visual Portfolio Tracker
[size=2]Electron Flux for Android - Free Puzzle/Logic Game
razor1911
razor1911
maxgpgpu - this is great what you have done, and definitely I'll borrow some of your ideas to my engine - like the one where you 'mark' the objects 'modified' when any translation/rotation has been made - instead of calculating those every frame. Sometimes optimization is more like a 'logical' challenge than hardcore-asm-programming.




What i am thinking about now is what would be faster when a rotation/position of the object changes (marked 'modified')

- updating the VBO (this would require every model on the map to have it's own VBO)

- passing the mat4 every frame to the shader

?


Update: karwosts, so if there is no performance 'drop' when using fixed translate/rotate and instead of that those are only 'deprecated' - means, i don't need to worry about those too much when it comes to optimization.. correct?
razor1911
razor1911
Ok did the matrix solution and seems like no change in performance.




Here are the latest stats,

54499b7c.jpg




Running on:

NVIDIA 7900 GT/GTO with 4x MSAA - A2C - SSAO - SHADOWMAPPING - .. - POSTPROCESSING (includes Bloom)







Now, i have SSAO, SHADOWMAPPING and WATER REFLECTION rendered to separate FBO's.

what is usually faster - switching FBOS, or having one FBO and switching destination texture within it ?




maxgpgpu
maxgpgpu

maxgpgpu - this is great what you have done, and definitely I'll borrow some of your ideas to my engine - like the one where you 'mark' the objects 'modified' when any translation/rotation has been made - instead of calculating those every frame. Sometimes optimization is more like a 'logical' challenge than hardcore-asm-programming.

What i am thinking about now is what would be faster when a rotation/position of the object changes (marked 'modified')

- updating the VBO (this would require every model on the map to have it's own VBO)

- passing the mat4 every frame to the shader

?

Update: karwosts, so if there is no performance 'drop' when using fixed translate/rotate and instead of that those are only 'deprecated' - means, i don't need to worry about those too much when it comes to optimization.. correct?


Note: If you transform every object into world-coordinates in the CPU, there exists only ONE transformation matrix for 100% of vertices in the GPU. Therefore, once per frame you update the transformation matrix (a uniform variable). Then the GPU transforms every vertex with the same matrix. In other words, the GPU can literally transform every single vertex in every object to viewport (final display) coordinates with the same transformation matrix. That's what I do, in fact, unless too many objects to fit in my VBO are created... in which case I need to render all my objects with two or three or more calls of glDrawElements().

Of course, since my VBOs must contain vertices with valid world-coordinates, I must update the vertices of those objects after I transform them to world-coordinate by calling glBufferSubData(). But block transfers to the VBO are quite quick these days, given all good GPU cards have 16-bit wide PCIe [x2] buses. Note: I have exactly one IBO/VBO/VAO for each BATCH, and each batch contains dozens to hundreds of objects. My "small" batches have 65,535 vertices, for example (to function with 16-bit indices in the IBO AKA "elements array).
V-man
V-man

what is usually faster - switching FBOS, or having one FBO and switching destination texture within it ?


http://www.opengl.org/wiki/GL_EXT_framebuffer_object#1_FBO_or_more

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.