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

Is GetTransform() heavy?

Started by Nil_gm Jul 3, 2009 at 4:21 AM 9 replies 4.4k views
Original Post
Nil_gm
Nil_gm
In an early tech-demo of ours, we implemented a matrix stack by simply GetTransform() from the D3D device, multiply with current matrix, SetTransform(), when we try to pop matrix just SetTransform() with the old one. Because it is easy to use, this routine soon became heavily used throughout the whole app. So far we don't see much speed penalty, maybe because we only use it per-object, skeleton calculation has its own internal matrix update function. But I always wonder, is GetTransform() slow or does it cause a pipeline stall? P.S, we are not using pure device
jollyjeffers
jollyjeffers
Generally modifying the state under D3D9 is expensive, but I've not personally experience nor read many accounts of the GetTransform() statement being particularly slow.

You have the key advantage here that IF it turned out to be a bottleneck it is very easy to eliminate via a proxy layer between your real device and your matrix stack.

Accurately Profiling Direct3D API Calls (Direct3D 9) is probably the best article to read on the subject, and the appendix has a list of worst offenders. Note how none of the "Get" methods appears, but most of the "Set" ones do. Which isn't too surprising, but suggests that you want to optimize the SetTransform() calls more than the GetTransform() ones [wink]

hth
Jack
<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ |
Evil Steve
Evil Steve
No, it won't cause a pipeline stall. A non-pure device just buffers the calls to SetTransform() and GetTransform(), so you'll just be accessing a member variable of the device class most likely.

If in doubt, profile it and see - I doubt you'll see GetTransform() taking more than a few CPU cycles. Or, you could do the buffering yourself, which may be ever so slightly faster - but would allow you to use a pure device if you wish later on.

Also remember that if the device is lost and reset, GetTransform() will return an identity matrix since the transforms will all be reset on device reset.
Nil_gm
Nil_gm
Thanks for the response. Also our render manager class does not buffer all the render state changes for now, only some key states, like blending, culling, fog, etc., it is mostly for logically group the primitives to get the correct rendering. We have been testing a lot of shader effects, so we have a lot of SetPixel/VertexShader and SetTexture(). I know those calls are quite slow according to the profiling, but we sort all those calls by shader object and texture, so quite often the Set calls are just setting the same value as last call, is this slow on a non-pure device?
Nil_gm
Nil_gm
any ideas?
Evil Steve
Evil Steve
Quote:
Original post by Nil_gm
Thanks for the response. Also our render manager class does not buffer all the render state changes for now, only some key states, like blending, culling, fog, etc., it is mostly for logically group the primitives to get the correct rendering. We have been testing a lot of shader effects, so we have a lot of SetPixel/VertexShader and SetTexture(). I know those calls are quite slow according to the profiling, but we sort all those calls by shader object and texture, so quite often the Set calls are just setting the same value as last call, is this slow on a non-pure device?
When you specify a pure device, D3D just passes all calls straight to the driver and doesn't buffer them. That means that Get*() isn't supported, because those functions aren't exposed by the driver; D3D buffers them normally. All that means is that a pure device can sometimes be a little faster because D3D doesn't need to do any buffering. As such, you're not going to see any noticeable difference.

So long as you short by shader and texture, there's not much of a problem. The main thing is to reduce the number of batches as far as possible, you only want a few hundred (No more than 500 or so) per frame, or you're going to have performance problems.
Nil_gm
Nil_gm
What I mean is, if i work on a non-pure device, and I sort primitives by shader and texture, when first I do SetTexture(tex0), draw something, directly after that, I do SetTexture(tex0) again, then draw another primitive, will the second SetTexture() cost as much as the first one(notice the texture pointer is the same tex0), or D3D just check its buffer, see it is the same texture and ignore the Set operation?
Evil Steve
Evil Steve
Quote:
Original post by Nil_gm
What I mean is, if i work on a non-pure device, and I sort primitives by shader and texture, when first I do SetTexture(tex0), draw something, directly after that, I do SetTexture(tex0) again, then draw another primitive, will the second SetTexture() cost as much as the first one(notice the texture pointer is the same tex0), or D3D just check its buffer, see it is the same texture and ignore the Set operation?
It depends on the driver - almost certainly not. The performance impact comes from making a transition to driver-mode to make the call, rather than the driver not buffering the call.

It's still a fairly minor impact unless you're making thousands of Set*() calls per frame - if in doubt, profile it and see.
flyfish_bit
flyfish_bit
Notice this sentence in Accurately Profiling Direct3D API Calls (Direct3D 9) from MS which jollyjeffers give us,
"Notice that the code sample requires an array of two textures. To avoid a runtime optimization that would remove IDirect3DDevice9::SetTexture if it sets the same texture pointer every time it is called, simply use an array of two textures. "

They said there is a runtime optimization that "to remove IDirect3DDevice9::SetTexture if it sets the same texture pointer every time it is called". So if you call SetTexture(tex0) twice, only one call can be sent to driver level.

[Edited by - flyfish_bit on July 8, 2009 12:28:17 AM]
Evil Steve
Evil Steve
Quote:
Original post by flyfish_bit
They said there is a runtime optimization that "to remove IDirect3DDevice9::SetTexture if it sets the same texture pointer every time it is called". So if you call SetTexture(tex0) twice, only one call can be sent to driver level.
However, it doesn't say anything about a pure device - if the code was tested with a pure device, there'd be a small performance difference due to the transition to driver-mode.
flyfish_bit
flyfish_bit
Yes, pure device is different.Here is an article describes these things,

What's the best way to do state changes? . I paste the text bellow:

Remember - there are three sides to this question. (1) what the app does, (2) what the D3D layer does and (3) what the videocard driver does. All affect performance in different ways. A few facts:


On a PURE device, SRS and STSS calls go straight to the driver (or rather, they go into the D3D command-buffer that goes to the driver).


On a non-PURE device, they are filtered by D3D. Redundant calls (i.e. changes to the same value) get filtered. Non-redundant values are added to the command-buffer.


On almost all drivers, when they get a state change command, they simply remember it. When they get a DIP-style command, then they think about setting states and so on.


I believe all IHVs now handle state blocks well. Older nVidia drivers do support state blocks natively, but with an eccentric and slow implementation. Careful about them.


On drivers that don't handle state blocks natively, the D3D runtime expands them internally into multiple SRS and STSS calls, filters redundant ones, and adds the others to the command-stream.


On drivers that handle state blocks natively, on a PURE device, the D3D runtime just puts the "SetStateBlock" command into the command buffer. It does not snoop it for redundant states.


On drivers that handle state blocks natively, on a non-PURE device, I think the D3D runtime snoops the state block command for the renderstates it contains, updates its internal settings (so it can filter out future redundant states) and passes the SetStateBlock command into the command buffer.


Drivers that handle tate blocks natively will either expand into SRS and STSS states internally (only slightly more efficient than reading them from the command-stream), or will do UltraCunningThings that minimise bus traffic and CPU time and all that malarky. Note that redundant states stored in the state block will be vaped very very quickly in either case.


From these facts we can see that:


Redundant state sets get vaped by the D3D layer, except on PURE device. But you cost yourself an app->D3D call.


Redundant state sets and multiple state sets (i.e. the same state, set several times, to different values) get vaped by the driver. But you cost yourself an app->D3D call and an entry in the command-buffer (a tiny cost).


All states are set at the same time in the driver. For this reason, most IHVs say that changing a single state is expensive, but changing lots of states at the same time is not much more expensive.


Using state blocks at worst saves you multiple SRS and STSS calls. At the best, it goes really fast and stuff.


So, personally I use slightly-wrappered state blocks. If I detect that the driver is shockingly slow at state blocks (some old nVidia drivers are), then I switch to emulating them myself. Essentially, I do what the D3D runtime does for drivers that don't support them - I expand them out into SRS and STSS calls (culling redundant calls of course). This is extremely fast to do - it is faster than the conventional wrapping of SRS and STSS calls that people do, because all the setting/culling is done in one single tight loop, not spread all over the code.

I have a big chunk of code that shows how to effectively capture and replay state to/from state blocks. So you can set your states up conventionally using SRS/STSS calls, then capture them into state blocks (done at start of day). This code can also serve as the replay of state blocks when the driver doesn't like you just calling SetStateBlock, although in practice I use a much tighter loop with redundancy checking, but it is a very similar structure. At the moment you can find it in the DirectX mailing list archives here (if that doesn't work, just search for the word "ADDRENDERSTATE" in the archives). I'll put it somewhere more sensible in time.

(SRS = SetRenderState, STSS=SetTextureStageState. There are other state-setting calls as well such as SetTransform - include them in the above comments).

Topic Locked

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

Sign in to reply to this topic.