How do you handle HRESULT errors?

Started by
5 comments, last by TomKQT 12 years, 8 months ago
Hello,

As you know most of the DX methods return a HRESULT status code.
Some error codes are obviously important. For example you can't ignore if creating a VertexBuffer failed. But I am not sure how I should treat
the HRESULT return value of functions that are not THAT important.

One example:
void D3D9RenderSystem::_setTextureMinFilter(uint samplerIndex, TextureFilter::Type texFilter) {
mDevice->SetSamplerState(samplerIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
};

This method sets the sampler state in my engine. Should I check for the HRESULT of SetSamplerState()? And more important: What should I do if I
get an E_ERROR? I am not using exceptions (since someone told me they are not used on consoles) and this method is very deep (callstack wise) in my render loop. I had to give millions of methods bool values and make tests everywhere - would be a complete mess!

So how do you treat HRESULT return values? And what do you recommend in this particular case? (And btw: Are you guys using exceptions?)
Advertisement
If a sampler could not be created, store a very specific and explaining error message in a thread safe message queue and continue with a default sampler.
Always include the class and method name in the beginning of the message in case of accidentally having the same message on multiple locations.
Functions and macros can be used for reporting errors to include as much information as possible without taking space or CPU time.
Also remember to check array bounds, trap access violation on every line that use unsafe code and check if the pointer is NULL after allocating in case of running out of memory.
Reduce the amount of code by throwing out DXUT and other bloated things.

You can use this to present the name of a HRESULT if you get an unexpected value.

char* NameOfHRESULT(HRESULT error) {
if (error = S_OK) {
return "S_OK";
} else if (error = E_ABORT) {
return "E_ABORT";
} else if (error = E_ACCESSDENIED) {
return "E_ACCESSDENIED";
} else if (error = E_FAIL) {
return "E_FAIL";
} else if (error = E_HANDLE) {
return "E_HANDLE";
} else if (error = E_INVALIDARG) {
return "E_INVALIDARG";
} else if (error = E_NOINTERFACE) {
return "E_NOINTERFACE";
} else if (error = E_OUTOFMEMORY) {
return "E_OUTOFMEMORY";
} else if (error = E_POINTER) {
return "E_POINTER";
} else if (error = E_UNEXPECTED) {
return "E_UNEXPECTED";
}
}
The standard is to wrap all calls that return an HRESULT in a define. For example, this is what I use

#if defined(_DEBUG) || defined(DEBUG)
#pragma comment(lib, "dxerr") // needed for dxtrace
#ifndef HR
#define HR(x){ \
HRESULT hr = (x); \
if(FAILED(hr)){ \
std::cout<<"An error occured on line"<<(DWORD)__LINE__<<" in the file "<<__FILE__<<std::endl; \
std::cout<<DXGetErrorStringA(hr)<<std::endl<<DXGetErrorDescriptionA(hr)<<std::endl; \
} \
}
#endif
#else
#ifndef HR
#define HR(x) (x)
#endif
#endif


this works perfectly because in debug mode, you will be examining the return to the calls, but in release mode, the code will do nothing. The calls to DXGetErrorStringA and DXGetErrorDescriptionA give usefull information... most of the time anyway.

So your code above would be this:


void D3D9RenderSystem::_setTextureMinFilter(uint samplerIndex, TextureFilter::Type texFilter) {
HR(mDevice->SetSamplerState(samplerIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR));
};
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
At home, I use a function that throws an exception on failure. At work, we mostly use a macro that raises an assert for debug builds and that does nothing for release builds. For most cases just turning on the debug DX runtimes will do a way better job than you ever could with asserts or exceptions, because it will give you a detailed as to why your call failed. At work the only case where we use more robust error handling is for creating a device and setting up the swap chain, so that we can report a useful error message if the user is not running on a PC with a good enough GPU + version of windows. But for something like SetSamplerState the only time that will fail is if you've screwed up something on the code side of things, and in that case you will be better off with the output from the debug runtimes than you will be from breaking on a failing HRESULT.
I see.
Since the debug output from DX is pretty good (most of the time) I never thought about using a macro in debug mode. I just wondered what I should do
in release mode.

So I guess its ok to only handle HRESULT error for really critical functions like CreateTexture, but not all the 100 functions that all called in the render loop.

Another question: Do game developers use exceptions? Actually I like them but I have read some articles that claim AAA games do not use them because of the overhead. Since I want to run my game on the Xbox360 (in the far far future) I decided to now using them. But error handling without exceptions is sometimes really an ugly if() mess:/
Depends on the context. Sometimes I handle a FAILED HRESULT by a fallback to something that's more likely to work, sometimes I need the HRESULT to proceed (if (SUCCEEDED (tex->LockRect (...))) // update the texture and unlock), and sometimes I do nothing.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

I personaly use a makro similar to what smasherprog showed, but I have two versions - HRD which stays only in debug builds (so it's similar to the smasherprog's) and HR which stays also in release builds (for some really critical operations).
And in the makro I throw an expection:

#define HRD(x) { \
HRESULT hr = x; \
if (FAILED(hr)) { \
throw Exception(hr, #x, __FILE__, __FUNCTION__, __LINE__); \
} \
}


Plus what mhagain said - it some situations I handle HRESULT directly at the place, to either make some alternative when something fails (create other surface format etc) or to simply continue or not (in the case of Lock).

This topic is closed to new replies.

Advertisement