Direct3D Programming Tip #4: Check All HRESULTs

Checking for errors is a good programming habit, but new programmers haven’t yet learned this habit. Most of the methods and functions you can call in Direct3D return an HRESULT, or a COM “handle to a result”, that indicates success or failure. Most of the time, your expectation is that these calls should succeed. This leads people to write “happy path” code that assumes that the call will succeed instead of verifying that it succeeded.

Use the FAILED and SUCCEEDED macros to turn an HRESULT into a boolean value that can be used in a conditional statement to determine the success or failure of any given HRESULT. Its not a good idea to compare the return value to specific failure codes because those failure codes may change and then your comparison logic will be wrong. The macros above tell you the general status of an HRESULT: success or failure.

Just as there are multiple failure codes that can be encoded in an HRESULT, there are also multiple success values. Comparing the result code to S_OK can fail for the same reasons that comparing the result code to a specific failure code can fail.

When programming in Direct3D, small errors early in the use of the device tend to manifest themselves as an incorrect rendering that’s only noticed much later after the source of the problem. If you check all your HRESULT values diligently, then you will be notified at the source of the problem and not later when the incorrect rendering is presented to the screen.

A failure to check the result codes of methods and functions is a common problem among beginning Direct3D programmers. It distracts them from the true source of the error. Checking the result codes diligently will keep you focused on the error when it occurs instead of having to backtrack in the debugger.

8 Responses to “Direct3D Programming Tip #4: Check All HRESULTs”

  1. Dave Wall Says:

    Worth noting that some functions return S_FALSE or E_FAIL (even if the documentation says otherwise) which break the SUCCEEDED and FAILED macros.

    The ones I’ve seen are:
    Direct3DQuery9::GetData(…) this is documented and is a case you should be testing for anyway.
    IDirect3DSurface9::GetContainer(…) returns E_FAIL if the RIID doesn’t match the type.
    Direct3DDevice9::GetBackBuffer(…) returns E_FAIL if there is not a backbuffer.

    The last two may only happen during a PIX capture, I’ve not fully investigated.

    Dave

    Like

  2. legalize Says:

    S_FALSE and E_FAIL don’t “break” the SUCCEEDED and FAILED macros, I don’t know why you would say that. It is true that sometimes you need to check the specific HRESULT returned, but for the typical case just checking success or failure is all you need to do. Also, you can’t have a device without a back buffer. Its simply not possible because you must create a default swap chain with a device and a swap chain must always have at least one back buffer.

    Like

  3. Dave Wall Says:

    My mistake on E_FAIL, I don’t consider the failure to get information from a query to be a success although fine rereading of docs reveals that Microsoft do.

    I’ll double check what is going on with GetBackBuffer(…).

    Dave

    Like

  4. legalize Says:

    IDirect3DQuery9::GetData returns S_FALSE when the data you’ve requested isn’t available yet because the query hasn’t been processed. Its not an error, because the API was correctly invoked. This is an example of an API method that has two possible success codes: one indicates that the API was properly called and the data is available and the other indicates that the API was properly called and the data is not yet available. This is standard COM style status code handling. Its not an error just because the data isn’t ready yet. In this particular case, you might do something like:

    HRESULT status = query->GetData(data, dataSize, 0);
    if (FAILED(status)) throw status;
    if (S_OK == status) processData(data, dataSize);

    IDirect3DDevice9::GetBackBuffer only fails when you pass it incorrect arguments.

    Like

  5. haust Says:

    Since HRESULT is a numeric it won’t be any help if you don’t understand it :)

    You can use DXGetErrorString, DXGetErrorDescription and/or DXTrace. You’ll have to include Dxerr.h and add Dxerr.lib.

    Like

    • legalize Says:

      While this is true, if you’re using the debug runtime, a message is always printed when the failure is generated, so it should be straightforward to identify the cause of the problem without calling these functions.

      If you are trying to cope with unexpected errors during testing or on a user’s machine, then these functions are useful for writing into your error log.

      Like

  6. NoiseGrinder Says:

    If I check the HRESULT in every frame, will it affect FPS?

    Like

    • legalize Says:

      There are two types of errors you need to check for: those indicating a programming/logic error and those indicating an expected problem at runtime, such as the device being lost. The mechanism that I use encapsulates the error checking inside a macro, THR. The macro expands file and line and evaluated expression into a string that can be used for debugging diagnostics. You can easily have this macro do different things in debug and release builds. You’ll want to exhaustively check all HRESULTs to detect programming/logic errors during development. When you are ready to release and are confident that your logic errors have been eliminated, you can just worry about things like device lost.

      Trying to guess what will affect the machine in regards to performance (FPS or otherwise) without data is a pointless optimization, often referred to as “premature optimization”. You won’t know what affects FPS until you measure it. Until you have measured it, you should write code that checks all errors and is written in the simplest fashion.

      In general, my advice is to setup continuous integration processes that measure performance benchmarks (i.e. FPS) if that is a requirement for your application. Then, when the code changes to violate your performance constraint, you can adjust as necessary. Fast and wrong is still wrong. You need to check for logic/programming errors in your application as you develop it and the only way to do that is to check the HRESULTs.

      Like


Leave a reply to legalize Cancel reply