Skip to main content
gamedev.net
🔒 Locked

Locking a dynamic vertex buffer...

Started by Phantom Sage 5 replies 3.3k views
Original Post
Phantom Sage
Phantom Sage
So I finally did some playing around with PIX today (yeah, I know I'm behind the times) and I found my library (which is for 2D stuff) is locking its dynamic vertex buffer more than once per frame and the number varies depending on how many state changes there are. In the worst case with my test app it was locking the buffer around 11 times per frame (for something like 4000 sprites). Now, before I go tearing apart my renderer and perhaps causing a maintenance nightmare for myself I have to ask: Knowing that I should aim for the least number of locks possible per frame, is 11 locks per frame really all that bad? Is it worth the incredibly time consuming effort to try and knock it down to one or two locks per frame? Or should I only be concerned if I get to 100+ locks per frame? I know how to fix it up to get it down to a minimum number of locks, but it's going to end up being a rewrite of a sizable (and quite stable) chunk of code. I want to know if it's really worth it.
AshleysBrain
AshleysBrain
If you are sending a reasonably large number of vertices every time you lock, providing you specify D3DLOCK_DISCARD when locking (and D3DUSAGE_WRITEONLY if it applies when creating) then locking is not all that expensive. Providing you're not locking and unlocking a handful of vertices and all that frame's vertices are spread evenly between them, you can probably get away with 11 locks. The slow bit is when it has to actually send the vertices to the GPU when you unlock.

On the other hand, you don't need to lock/unlock vertices just because of state changes. You can call DrawPrimitive multiple times on different parts of the same vertex buffer with state changes in between - without having to lock and unlock the vertices.
Construct (Free open-source game creator)
Phantom Sage
Phantom Sage
Quote:
Original post by AshleysBrain
If you are sending a reasonably large number of vertices every time you lock, providing you specify D3DLOCK_DISCARD when locking (and D3DUSAGE_WRITEONLY if it applies when creating) then locking is not all that expensive. Providing you're not locking and unlocking a handful of vertices and all that frame's vertices are spread evenly between them, you can probably get away with 11 locks. The slow bit is when it has to actually send the vertices to the GPU when you unlock.

Sometimes it's a large amount, sometimes not. It all depends on how much is batched between frames. Since it's a library that's really up to the user to handle. But there would be times where it be only sending a miniscule amount of vertices. How much depends on what the user is doing at the time.

Quote:
Original post by AshleysBrain
On the other hand, you don't need to lock/unlock vertices just because of state changes. You can call DrawPrimitive multiple times on different parts of the same vertex buffer with state changes in between - without having to lock and unlock the vertices.

Yeah that's what I had come up with as an alternative, it's just going to be hell ripping out that old code and putting it in. But I only need to do it once, so I might as well look into it and profile.

Thanks for the reply.
sirob
sirob
Locks calls are not as expensive as they are made to sound. Rather, they're not always that expensive.

The difficult thing will lock calls is that they're pretty much the only call that can actually stall your pipeline. If you lock a resource that was used in a previous draw call, you'll wait until that call completes (unless you lock with NOOVERWRITE or DISCARD). That can be a very long time compared to any other D3D call you can make (Lets ignore Present since it stalls in a different way).

So, really, you shouldn't be worried about the number of Lock calls you have (at least not when the number is 11). What you should be worried about is what you're locking, and how long it will be before the resource is no longer in use (hopefully, that should be before you make the call).

You can also use NOOVERWRITE and DISCARD to speed up some locks, and avoid major stalls in some cases, but use them with caution.
Sirob Yes.» - status: Work-O-Rama.
clb
clb
ProfileProfileProfileProfile*breatheee*ProfileProfile...

Are you CPU or GPU-bound? Locking a dynamic buffer can slow things down in two ways:
1) The GPU is using the buffer at the moment you call Lock(). If the driver is unable to assist you (e.g. give you another buffer if you had D3DLOCK_DISCARD), the Lock() call will cause the CPU to wait until GPU is finished with the buffer. Whether this kind of behavior occurs is easy to profile - just examine how long a call to Lock() takes. If the Lock() call returns immediately, you're ok.

2) The Lock() call returns immediately, but while you're working with the locked buffer, the GPU finished all its work and is now starving. Use nVidia PerfKit or AMD GPU PerfStudio to get counters on how much time the GPU is idle. To alleviate this, you could prebuild the buffer data to RAM and update the buffer with a quicker lock-memcpy-unlock sequence, and possibly try to get some time between the buffer update and render commands.

In a situation where you had 0 dynamic locks per frame and where you were GPU bound, you'd typically see all the CPU time being waited in Present(), but when you introduce a single lock, you might see all that CPU waiting time shifted from Present() to Lock(). Now when you profile and see all this time being spent in Lock(), it can be hard to distinguish whether it's because of suboptimal buffer use or whether it is just because GPU's got too much to work on.

If you profile both of the above happening, then you probably can squeeze a gain out from smarter locking patterns. Generally in an optimal situation you should see only 1) or 2) happening, but not both.

Additionally, the number of frames you allow the device to render up ahead can affect where the CPU stalls to wait for GPU. To profile the impact of dynamic buffer updates I typically limit to double buffering and make a runtime-toggleable switch that can be used to control whether the buffers are updated at all. This way I can estimate the rendering speed impact in realtime.
TheGilb
TheGilb
Quote:
Original post by Tape_Worm
I know how to fix it up to get it down to a minimum number of locks, but it's going to end up being a rewrite of a sizable (and quite stable) chunk of code. I want to know if it's really worth it.
Optimizing can be a fun learning exercise, but it seems to me like the reason why you're finding it difficult answering this question yourself is because there is no definitive target set.

Many of the latest GPU optimization techniques help to minimise per-frame state changes. John Carmack has even gone so far as to claim that, theoretically speaking, the idTech5 engine could render the entire world and everything in it in just 1 pass with no state changes.

The best advice I can offer you is to write the game first, get all the assets in, and then optimize after. You can optimize forever if you want, and spend your whole life making the code run as fast as can be - there really is no limit, just diminishing returns .. Or you can say "right, I want this to run at 60 fps, where's my first bottleneck?" and then that's a realistic target. As it stands, your code works, it's stable, so I say, put it to work for you!

Hope that helps, and good luck! :-)
Phantom Sage
Phantom Sage
Quote:
Original post by TheGilb
Optimizing can be a fun learning exercise, but it seems to me like the reason why you're finding it difficult answering this question yourself is because there is no definitive target set.

The best advice I can offer you is to write the game first, get all the assets in, and then optimize after. You can optimize forever if you want, and spend your whole life making the code run as fast as can be - there really is no limit, just diminishing returns .. Or you can say "right, I want this to run at 60 fps, where's my first bottleneck?" and then that's a realistic target. As it stands, your code works, it's stable, so I say, put it to work for you!

Hope that helps, and good luck! :-)

The only thing is that I'm not writing a game. Just writing a general purpose 2D library for public consumption. So that makes it extremely hard to determine a target because I don't want to constrain the users any more than I already have.

Of course optimization is going to be an ongoing thing and as such I rewrote a section of the library rendering code to minimize the locks (and data transfer) down from 6 (with 100 sprites) to 3 and I can get it down further, but since this is only one section it's got a few extra going for now. However, the result was not encouraging on my really shitty Intel x3100 laptop chipset. With 1000 Sprites (plus text, and a few other sprites):
Before my "butchering"
  • Frame delta - ~23-25 ms
  • FPS - ~45 FPS
After the "optimizations"
  • Frame delta - ~33-36 ms
  • FPS - ~30 FPS
About a 30% drop. I don't know if I'm doing something wrong (very probably), but it's hardly encouraging. I did confirm that it is indeedily reducing the number of locks. I'll have to look into it further later this evening... TMZ is on and I must flee.

Topic Locked

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

Sign in to reply to this topic.