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

The Tree Batching Challenge

Started by Promit Jul 4, 2004 at 5:13 PM 13 replies 3k views
Original Post
Promit
Promit
This is not so much a question as a problem I was considering, and I want to generate some discussion about it. Background: We have some sort of outdoor environment. The specific nature of how we load, render, etc. this environment is unimportant; leave it as a generic terrain engine. We want to render simple trees on this terrain. Any given tree is very simple. It uses two quads crossed together in a '+' shape. Both quads use the same texture. A tree is completely static and its position is known before rendering. There is absolutely no CPU load involved in rendering, since the tree is not billboarded. The GPU load for rendering the 4 triangles required to draw the tree is also negligible. We want to render many of these trees, on the order of several thousand. We have a fast algorithm to quickly determine which trees are visible at any given time. Here's the catch. Although a tree is static, it is not necessarily "alive". On collision with the player object (think something big, like a tank or a mecha) a tree will be destroyed and removed from the world. We know at any given time the set of trees which are alive and visible. The Problem: We need to render these trees. We cannot afford to spend one vertex buffer and one draw call per tree per frame, as that would result in several hundred inefficient draw calls in any given frame. You need to solve this problem and come up with a scheme to reduce the total number of draw calls and vertex buffers as much as possible, in order to maximize throughput. Remember, the GPU and CPU are more than adequate to process your geometry, and visibility determination is not a factor. However, it's necessary to draw around a thousand trees (on average) without using a thousand draw calls, and certainly without using a thousand vertex buffers. Rules: You are free to organize the data into vertex buffers, index buffers, and draw calls however you wish. However, you must use buffers and you are not allowed to use immediate mode. In short, glVertex* is off limits. I'm not looking for any actual code samples here; you need to describe your design and setup, not dump code. This is not a competition to see who has the best stock code sitting in a library somewhere. You cannot change the format of the trees. No billboards, no full models. You can only work with the specific '+' shaped tree that I described earlier. Of course, this includes using any sort of LoD scheme. Vertex shaders are allowed, but you are limited to VS 1.1 techniques in hardware. Again, remember that this is a description of an rendering system/design rather than code. It doesn't matter whether you use assembly, HLSL, or Cg. I don't care. A software simulation of newer vertex shader versions is fine, but keep performance in mind. No vendor specific tricks or extensions. Additionally, no API specific tricks. Your design must be implementable on ATI and NV; it must be implementable on D3D and OGL. Whatever you do must be visually correct. For example, if a tree is dead, it better not be seen by the player. You're free to dynamically generate anything you want. However, stay within "reasonable" limits of CPU, GPU, and AGP usage. The same applies to RAM usage, video RAM usage, and static buffers. You're allowed to specify any given buffer as being in video memory or system memory, as you see fit. You're also allowed to let the API decide, if you wish. Hints: (These hints do not necessarily point to the "best" solution. They are intended to encourage you to think.) Just because a tree isn't alive doesn't mean you can't send it to the graphics card. Remember that there's more ways to make something vanish than simply not rendering it. When you're rendering from a vertex buffer, you can offset to anywhere in that buffer. The goal here is to achieve maximum performance and memory efficiency. This is not a practical demonstration, so don't waste your time trying to optimize for NV or ATI or whatever. Pulling everything into a single vertex buffer is not that hard. Anyone who is familiar with graphics techniques can do it. Reducing the number of draw calls should be your main goal. Remember, trees are static. Be careful when it comes to dynamic generation, and consider the costs and benefits of doing anything dynamically. Pretend this is part of a game. The more CPU you can leave free, the better. Please, think through this carefully before answering, instead of writing a quick 2 line post. This is intended as a simple challenge problem, not a question. Don't suggest changes in tree style, etc. I'm not looking for someone to tell me how to implement this. I am trying to provoke thought and see what kind of solutions people can come up with. Try to be creative, to explore various solutions, and see what you come up with.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
mitchw
mitchw
Since you are dealing with just 2 quad trees, it seems like treating the rendering of trees as you would a particle system would be best (performance and flexibility). Use a dynamic vertex buffer (+ index buffer to remove 4 verts a tree) and fill it up per frame with the visible trees. The index buffer can be created and not touched again at start up. Since the primitives are so cheap, I would probably just cull at the leaf level (whatever you use, quadtree, octree, etc) rather than per-tree. If a leaf is visible, pump all "visible" trees into the buffer.
Onemind
Onemind
You could load all the trees into one huge vertex buffer, and then batch render all of them. If you wanted to get really high-tech, you could implement some kind of "frame cohesion" techique that would save the vertex buffer, and just add/remove specific trees. Just wondering - are you actually making a 3D Engine, or is this just a logic game?
Promit
Promit
It's just a logic game. Don't forget that a tree can be destroyed at any time.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Raloth
Raloth
I think mitchw is right. Just mimmick a particle system.
____________________________________________________________AAAAA: American Association Against Adobe AcrobatYou know you hate PDFs...
Ysaneya
Ysaneya
How many "serveral thousand" trees do you want to display ?

If video memory usage is not a concern, i would advise making a static vertex buffer and a dynamic index buffer. Determine which trees are visible and just fill the index data, never touch the vertex data. If a tree is made of 2 quads, and given the usual 32 bytes per vertex, then 100k trees would take 25 Mb of video ram.

If you want to be more smart you can probably split this huge vertex buffer in multiple smaller ones, grouped by location in the world, and only render those that are in the viewer range.

Y.
Etnu
Etnu
2 vertex buffers -- 1 static, 1 dynamic.

The dynamic buffer would only contain color data.
The static buffer contains positional data.

Set your API to reject pixels on alpha values of 0, and to blend between texture + vertex alpha.

When a tree is created, set it's vertex alpha to 0xFFFFFFFF
When a tree is destroyed, set it's color to 0x00000000.

That will have the effect of making *ALL* pixels in the model 0 transparency.

This avoids the overhead of passing entire vertices over the bus every frame, which would probably be more expensive than drawing a bunch of invisible triangles. The only overhead you have is a single DWORD value per vertex.
---------------------------Hello, and Welcome to some arbitrary temporal location in the space-time continuum.
rick_appleton
rick_appleton
split up all the trees into chunks as mentioned before (size to be determined).

For each chunk of trees create:
- 2 buffers for each chunk, both static, one for the vertices, and the other for the indices.
- an array of scaling values, 1 value for each poly (so two values per tree).

Using a vertex program, set the program to accept the dynamic scalings, and scale each tree with the give factor. If the tree is dead, the scaling factor for that tree is set to 0.0. This gives you two float per tree of dynamic data, the rest is static. Of course chunks can be moved in and out of video ram at will.


edit: somehow my linebreaks aren't converted :(

[Edited by - rick_appleton on July 5, 2004 1:25:04 PM]
jamessharpe
jamessharpe
What I think I would is use a vertex shader to transform the model. to the position. Then we keep a static vertex buffer, consisting simply of the vertices for one tree at the origin, then we duplicate the indices for however many trees we need. We then use a vertex attribute to specify the position, and simply translate in the vertex shader. Then all we need to change is the stream of positions and the number of trees i.e. size of index buffer.
Promit
Promit
Cool.

I think the thread's mostly run its course, but there's 5 or 6 different and elegant solutions here to a fairly fundamental problem, which also relates extremely closely to particle systems, as pointed out.

For those reading, this might be one of those threads to archive for later reference [smile]

Thanks for taking the time, everyone who replied.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
ngill
ngill
Quote:
Original post by Ysaneya
How many "serveral thousand" trees do you want to display ?

If video memory usage is not a concern, i would advise making a static vertex buffer and a dynamic index buffer. Determine which trees are visible and just fill the index data, never touch the vertex data. If a tree is made of 2 quads, and given the usual 32 bytes per vertex, then 100k trees would take 25 Mb of video ram.

If you want to be more smart you can probably split this huge vertex buffer in multiple smaller ones, grouped by location in the world, and only render those that are in the viewer range.

Y.


I'd do this, but also split the huge index buffer into multiple smaller ones, because if a tank is driving in one area, you wouldn't have to touch other index buffers, and that peice of memory can sit closer to the graphics card.

Also, I wouldn't do transparent polygon stuff, because I don't know if the hardware is tranforming and actually blending all that stuff. I'd be willing to bet that it's still transforming 100s of thousands of triangles. And, I am not sure if it'd still try to render those polygons and blend them, so I'd make sure of that before implementing what Etnu suggested...
Etnu
Etnu
The problem with using an index buffer and a single vertex buffer is that you'd need to change the world transform and call your API's drawing function every single time you wanted to draw a tree. That many DrawIndexedPrimitive() calls would destroy a rendering pipeline in D3D, whereas the video card can easily push north of 30 million triangles per second...so the extra transformations really do mean jack shit.
---------------------------Hello, and Welcome to some arbitrary temporal location in the space-time continuum.
Promit
Promit
Quote:
Original post by Etnu
The problem with using an index buffer and a single vertex buffer is that you'd need to change the world transform and call your API's drawing function every single time you wanted to draw a tree. That many DrawIndexedPrimitive() calls would destroy a rendering pipeline in D3D, whereas the video card can easily push north of 30 million triangles per second...so the extra transformations really do mean jack shit.


Just to add to this, according to an nVidia paper, 50K called to DrawIndexedPrimitive per second will completely saturate a P4 1.3's abilities to push, which equates to a mere 1000 calls at 50 fps.

Batching is vital.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
ngill
ngill
Quote:
Original post by Etnu
The problem with using an index buffer and a single vertex buffer is that you'd need to change the world transform and call your API's drawing function every single time you wanted to draw a tree. That many DrawIndexedPrimitive() calls would destroy a rendering pipeline in D3D, whereas the video card can easily push north of 30 million triangles per second...so the extra transformations really do mean jack shit.


lets say you have 90 trees, in 9 index buffers.. [3x3 grid of each]

that'd be 9 DIP calls.. (not 90)

and each index buffer would have 10 trees.

and you can have all the visible trees in the front of the index buffer, and the invisible trees at the end of the buffer. that way if you remove a tree, you touch only 1/9 index buffers, and there you just have to do a swap operation.

(the numbers 90, 10, and 9 were examples.. you can scale that properly so that it's hardware friendly)
Ysaneya
Ysaneya
I forgot to reply to this thread, Etnu/Promit, i'm not sure if you were replying to my proposal or to somebody else, but here we go:

Quote:

The problem with using an index buffer and a single vertex buffer is that you'd need to change the world transform


Huh, no. Unlike a particle system, your trees are not going to change of position (unless they are treants). You just need to fill your vertex buffer data once, at load time, and that's all. At run-time, determine which trees are in your frustum and/or visbility range, and fill an index buffer with the indices for the visible trees. Render all your trees at once as a single batch (no set matrix here!). Filling an index buffer should be quite fast. Assuming you are not fillrate limited, i'm confident you'd be able to push 30 million tris/second with your trees on a decent video card.

This technique has one obvious drawback: if the vertex data is static, and if your world contains a lot of trees, it's going to cost you a lot of video ram. The raw implementation is not very efficient.

If you are really serious about it, i would suggest splitting your world in virtual sectors (a grid-based approach should work well). For each of these sectors, make a vertex buffer. When one of these sectors is in view and hasn't been streamed yet, generate your trees data (maybe procedurally) and fill the vertex buffer.

Let's do a few calculations; assume that your view distance is 2 km, that each sector is 100m x 100m, and that your world contains one tree every 10 m^2. That's a density of 1000 trees per sector. There's at any time around 100 sectors in the view frustum. That's data for 100k trees. If each vertex is 32 bytes, you're only using 3.2 Mb of data for your trees at most, at any time.. and if you have a lower density of trees, it's even less than that.

If you don't care too much about having perfect visibility, i would even suggest making one static index buffer per sector. That way, at render time, all your trees data is perfectly static and optimized for rendering. Your bottleneck might switch to trees data generation though, but since the data is cached, i'm quite sure you'd be unable to notice any performance drop.

Y.

Topic Locked

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

Sign in to reply to this topic.