Click here to Skip to main content
Click here to Skip to main content

Introducing Investigo: Using a Proxy DLL and embedded HTTP server for DirectX9 Performance Analysis, Debugging and Automated Performance Testing.

, 9 Nov 2012
Rate this:
Please Sign up or sign in to vote.
Introducing Investigo: Using a Proxy DLL and embedded HTTP server for DirectX9 Performance Analysis, Debugging and Automated Performance Testing

Contents

Introduction
Using Investigo
Implementation
Reference
Alternative Tools
Resources

Introduction

What is it?

Investigo is a software toolkit to aid in performance analysis and debugging of DirectX9 applications. It can measure the performance of DirectX applications and will also help develop your understanding of DirectX applications or more generally your overall understanding of DirectX.

Investigo is open source, released under the MIT licence and available at SourceForge.

Screenshots

Here is a screenshot of Investigo's Overview live performance graphs. In the Overview page you can monitor frame rate, frame time and draw calls for a DirectX 9 application.




The next screenshot shows Investigo's Debugger page. Click a button here for a particular DirectX function and a Visual Studio breakpoint is triggered the next time that function is called.




The next screenshot shows Investigo's Config page. It displays details of DirectX device configuration and capabilities.



Features at a Glance

  • Displays live performance metrics such as frame rate, frame time, number of draw calls.
  • Displays timing and call-counts for Direct API calls.
  • Extensible - allows application defined metrics and code timings.
  • Captures the performance metrics to log files for offline analysis and report generation.
  • Easily trigger Visual Studio breakpoints on DirectX API calls.
For potential future features, please see Future Features.

Who is it for?

Investigo is for anyone who wants to measure the performance of a DirectX application.

For programmers it can aid in understanding and debugging DirectX code. There is always a difficult learning curve when learning a new code-base. Investigo can help you get orientated by helping you develop a bottom-up understanding of the code-base, more on that later.

No really, what is it?

The core of Investigo is a proxy DLL that intercepts DirectX API calls. Its main purpose is to record real-time performance metrics generated by DirectX9 applications.

Investigo has a web UI that is delivered through an embedded HTTP server. The web UI displays live performance graphs that help you develop a broad understanding of the performance characteristics and bottlenecks in the application.

The live graphs are only intended as a quick guide to evaluating performance. When you are serious about performance analysis you need to capture performance metrics for offline analysis. Investigo outputs performance metrics to CSV files. A C# console app that I have included, called ReportGen, transforms the captured performance data into a HTML-based report, an example screenshot follows.



Investigo can help you build an automated performance testing system, but I'll save that for a later section.

For programmers, Investigo can be very useful, helping you become orientated, when you start working with a new DirectX9 code-base. It helps you develop a bottom-up understanding of the code-base and can accelerate your understanding of the application's DirectX usage. The web UI supports this by allowing you to click a button to trigger a Visual Studio breakpoint when a DirectX API function is called.



Of course you must be running the application under the Visual Studio debugger for this to work, otherwise the application will crash when you attempt to trigger a breakpoint.

As you use Investigo to plumb the depths of DirectX it may also help build your general understanding of DirectX.

Assumed Knowledge

This article is probably going to be most useful to you if you have some pre-existing understanding of DirectX and have a reason to conduct performance analysis of a DirectX application. This article is going to get very technical very quickly and will cover a lot of ground. I'll include many links to auxiliary sources of information.

If you want to get into the implementation of Investigo you will probably already have some level of understanding of C++ and the DirectX API. If you want to understand the implementation of Investigo's UI you will find it helpful to have some understanding of how Javascript is used to build web applications. Again I'll include lots of links to help you learn as you go.

What to expect, what not to expect

Out of the box Investigo can easily measure various aspects of performance and, as already mentioned, it can also trigger breakpoints in DirectX API functions.

From an application perspective Investigo is extensible. The application can control Investigo via its API and custom application metrics can be added that are displayed in the live graphs and output to the performance logs.

However, if you are a programmer you may want more out of Investigo. In fact there are many potential features that I had to leave out so that I could release this article and make Investigo public. It was a trade-off between getting Investigo out there and keeping it under wraps for longer. I decided to get it out there, see what people think of it, then continue my work if there is demand. I am also hoping that others will contribute to Investigo and help move it forward.

New features will be implemented in time, but for the moment you may have to get your hands dirty and hack in other features that you need. Before making modifications to Investigo, please read the Implementation section and don't be afraid to dive into the code.

If you make any useful modifications please contribute them back to the code-base on SourceForge.

Background

Investigo was born as a rather simple proxy DLL whose purpose was to help me understand and optimize the performance of a particularly complex DirectX code-base.

Experienced programmers know that profiling is essential prior to optimization. We need to build an accurate picture of the application's performance in order to identify and isolate the performance bottlenecks that are potential candidates for optimization.

Normally in situations like this I would have turned immediately to NVPerfHUD, although at the time I had just put together a new PC and only had an ATI graphics card. Using NVPerfHUD requires an NVidia card (usually I have both types of gfx cards in my PC). I couldn't use NVPerfHUD at the time, so instead I created a proxy DirectX DLL to help with my profiling tasks.

It was a pleasant side affect that the proxy DLL also helped develop my understanding of the code-base. I was able to set breakpoints on important DirectX API calls. This allowed me to answer questions such as where in the code-base does the call to Present come from?

The original version of Investigo was much smaller and simpler than the version I am presenting here. It didn't have a UI and the data wasn't complex enough to require buffering in multiple threads. Initially it just forwarded API calls straight to DirectX and kept some simple metrics such as draw calls per frame.

Over time the proxy DLL grew more sophisticated. I added various other logging features and hacked in features as I needed to use them. For example, once I hacked in the feature list the set of textures changes for frame X (a feature I hope to officially put back into Investigo in the future). Over time the hacks built up and the DLL's code-base became cluttered. I set about rewriting it with the intention of exposing the most common and useful features via a UI.

The result of that rewrite is the version of Investigo that I am now presenting.

Bottom-up vs Top-down Understanding

Top-down and bottom-up are two fundamental approaches to understanding code. Both have their place when learning a new code-base.

Investigo can help you develop your bottom-up understanding of a code-base. Normally when you approach a new code-base you probably learn it from the top-down. That is to say you would start with the main function, see what it calls, follow those functions to see what they call and so on working your way down the call hierarchy.

The bottom-up approach is the reverse, you develop your understanding starting from the bottom of the call hierarchy and work your way up. For example you might start with the DirectX Present function and see what calls it, then move to the next function up, see what calls it and so on developing your understanding as you move up the call hierarchy.

Investigo aids bottom-up understanding by allowing you to set breakpoints in DirectX functions to see where in the application they are called from and how they are used. In the original version of Investigo I set breakpoints directly in the proxy DLL code using Visual Studio. The newest version of Investigo allows breakpoints to be triggered by a button click in the web UI.

But again please remember that for the breakpoints to work the application must be already running under the debugger! Setting a breakpoint in an app that is not running under a debugger will crash the app.

Using Investigo

Getting Started

Let's get started using Investigo. It is as simple as unpacking a zip file then copying the proxy DLL and config file to the same directory as the DirectX application.

1. Download Investigo.zip from this article, or get the latest version from the SourceForge downloads page. The other download for this article is InvestigoSrc.zip which contains the source code, but you don't need the code if you just want to use Investigo.

2. Unzip the contents of Investigo.zip to a directory. You should see the following files:



3. Copy d3d9.dll (the proxy DLL) and Investigo.cfg (the Investigo config file) to the application's directory. These files should be placed next to the DirectX application executable file.

Investigo.cfg configures various aspects of Investigo and must be placed in the same directory as the d3d9.dll proxy DLL.

Please see the config file reference for details on the Investigo config file.

4. Is the application directory in Program Files?

If so you will need to edit the config file and change OutputDirectory to point to a different directory. Investigo doesn't have permission to write to Program Files (unless you run the DirectX app as Administrator) so it needs an output directory for which it does have write permission.

While you are looking at the config file you should also check that its path to the DirectX DLL is correct for your operating system.




5. Now you can run the DirectX application. Investigo should be running and you should see the Investigo HUD in the top left-hand corner.



If you don't see the HUD go back through the setup instructions and make sure you have the setup correct. Also make sure you are actually running a DirectX9 application, Investigo won't work for OpenGL or other versions of DirectX.

If anything goes wrong you should check Investigo.log in the output directory for errors and potential solutions.

If it refuses to work please leave a message for let me know via SourceForge.

6. Now open your browser and point it at localhost:8080. You should see the home page of the Investigo Web UI in your browser.



If you have trouble running the HTTP server on port 8080 you can change it by adding a HTTPPort line in the config file:



Investigo HUD

The Investigo HUD is a small overlay on top of the application's render window. It is always displayed when the Investigo proxy DLL is loaded so you know that Investigo is running.



The status of performance logging is displayed in the HUD:



Investigo Web UI

The Investigo proxy DLL has an embedded HTTP server. When you are running an application that has loaded Investigo you can access the web UI by pointing your browser at localhost:8080.

The web UI displays live graphs of performance metrics and allows you to interact with the Investigo DirectX proxy DLL.

The beauty of the web UI is that it works across the network. You can run the DirectX application on one PC and then view the web UI across the LAN on another PC or even on a tablet device. Just point the browser at the IP address of the PC that is running the application. Be aware though that your network router might block certain ports in the network, this may mean for example that you can't use Investigo over a corporate LAN and you will need to speak to your network administrator to unblock the port.



The various pages of the web UI are briefly described in the table below.

Home Start page for the app which contains links to the other pages.
Config A list that displays the configuration and caps of the DirectX device.
Performance Links to sub-pages that display the live performance graphs.
Debugger A filterable list of DirectX API functions, each of which can be clicked to trigger a breakpoint in Visual Studio.
About Information about Investigo and myself.

Live Performance Graphs

The live performance graphs show the value of performance metrics over past frames of rendering, or in other words over time. For example, the Overview graph for frame rate:



The numbers along the bottom of the graph indicate the frame number. The green vertical bar indicates the current frame and the place where new data will be inserted. The number to the right of the green bar indicates the current value of the metric.

Capturing Performance Data for Offline Analysis

The live performance graphs are intended only to be a rough guide to the performance of your application. For accurate profiling you must capture performance data for offline analysis. Investigo captures performance data to CSV files. Investigo organizes the performance data into groups for output to separate CSV files, collectively these log files are known as the performance log.

Here is a screenshot of an example CSV performance log:



These CSV files can easily be imported into Excel or Open Office for manipulation as a spreadsheet.

Performance logging is disabled by default and there are a number of ways to enable it. The most simple, but least accurate is via the web UI.

The button in the top-right corner toggles performance logging.



For more accuracy, eg specifying on which frame performance logging should start and stop, performance logging can be enabled via the config file.

For the best accuracy, especially if the application isn't deterministic, application-code can start and stop performance logging via the Investio API.

For complete performance measurement accuracy your DirectX application should be deterministic. That is to say that for each run of the application it should be in the same state at the same time when performance logging is in progress. It takes commitment to make a game or graphics application deterministic, the reward though is code that is easier to test, debug and profile.

Included in the download for this article is the ReportGen command line app. Once you have captured performance data you can run ReportGen from the command line to transform the captured performance data into a HTML-based performance report:


Following is a screenshot of a HTML-based performance report generated by ReportGen.



ReportGen is really only intended as an example program and reports it generatres are rather simplistic. There is a lot more that can be done with this in future versions of Investigo, for now though if you need extra features or for the performance report to look different, then you may need to get into ReportGen's code and start hacking.

Investigo API

The Investigo C++ API allows the application to interact directly with the Investigo proxy DLL.

The entire API is wrapped in #define INVESTIGO_ENABLED. This allows Investigo to easily be removed from production builds. To use the API you must define INVESTIGO_ENABLED in your application's main header file or preferably in its project settings.

If you omit INVESTIGO_ENABLED from your project settings you won't be able to use the Investigo API. In addition to the normal Debug and Release builds you will probably want to have an additional build that I normally call Profiling. The Profiling build is the Release build with INVESTIGO_ENABLED defined. The Profiling build often includes application specific code for profiling and performance monitoring.

Investigo Concepts

Before looking at the API let's have an overview of Investigo's concepts and data structures.

Variables

A variable is a named unit of data stored in memory by Investigo.

History is maintained for each variable. The default size of the history buffer is 1000 frames, although this can be set in the config file.

The history of each variable can be viewed in the live performance graphs.



Pages and Groups

Variables are organized into pages and groups.

A page is a collection of variables. Pages define how variables are organized and displayed in the web UI, they can be nested to form a hierarchies of pages.



A group is also a collection of variables, however unlike pages, groups cannot be nested. Groups organize variables for output to the performance log. Each group maps to a single CSV file that logs the history for all variables in the group.



Timer

A timer measures the execution time of a block of code. The result of the timer (in milliseconds) are output to a variable.



A timer is essentially just a variable, so its history can also be viewed in the live performance graphs.

API Basics

The API includes a set of macros, functions and interfaces that expose Investigo features to the application. To use the Investigo API you must #include InvestigoApplicationInterface.h.

INVESTIGO_ENABLED must be enabled in your project to enable the Investigo API. Investigo functions and classes are conditonally compiled in when INVESTIGO_ENABLED is defined, otherwise these functions and classes are not available.

Investigo macros evaluate to no-ops when INVESTIGO_ENABLED is not defined.

Investigo functions can only be called when INVESTIGO_ENABLED is defined, you should therefore wrap such calls in INVESTIGO_ENABLED so they can be compiled out of production builds:

#ifdef INVESTIGO_ENABLED

 // ... make calls to Investigo functions ...

#endif // INVESTIGO_ENABLED

In addition, the macros and functions from the API are only effective when the d3d9.dll proxy dll is placed in the same directory as the calling application. When the DLL is not present Investigo macros and functions do nothing.

Application Defined Variables and Performance Metrics

The application can define its own variables and timers that can be viewed in the web UI and are output to the performance log (when it is enabled). These macros completely abstract the interface to the Investigo DLL.

Variables

Variables can be set at any time. To set the value of an integer variable:
int value = 10;
INVESTIGO_INT_SET_VALUE("MyPage", "MyVariable", value);

To increment the value of an integer variable:
INVESTIGO_INT_INCREMENT("MyPage", "MyVariable");

To reset the value of an integer variable to zero:
INVESTIGO_INT_RESET("MyPage", "MyVariable");

Similar macros exist for setting and reseting double variables, however there is no increment macro for double variables.
Timers

The INVESTIGO_TIMER macro is used to profile a block of code and determine its execution time.

{ // Start of code block.
 INVESTIGO_TIMER("MyPage", "MyTimer");

 // ... code to profile ...

} // End of code block.


Investigo Inline Functions

The API provides a number of global inline functions that interact with various other Investigo feaures. These functions abstract the interface to the Investigo DLL, so you can call them and not have to worry about loading or unloading the Investigo DLL.

For example, to set Investigo's output directory:

const char* newOutputDirectory = ...
Investigo::Config::SetOutputDirectory(newOutputDirectory);

To start and stop performance logging:

Investigo::Performance::StartLogging()

// ... some time passes ...

Investigo::Performance::StopLogging();

To enable and disable draw calls:

Investigo::Experiements::DisableDrawCalls();

// ... do some rendering ...

Investigo::Experiments::EnableDrawCalls();

Investigo inline functions have no effect when the Investigo DLL is not present. If they are unable to load the DLL they do nothing and on subsequent calls will not re-attempt to load the DLL.

Investigo Resource Annotation

The Investigo API provides functions that allow DirectX resources to be named.

To set the name of a DirectX resource from application code, surround the resource loading code with calls to the macros INVESTIGO_RESOURCE_BEGIN and INVESTIGO_RESOURCE_END. These macros simply wrap the function Investigo::ResourceBegin and Investigo::ResourceEnd and are only compiled in when INVESTIGO_ENABLED is defined.
Calls to these functions inform Investigo of the names of resource(s) that are being loaded. Investigo annotates each loaded resource, eg textures and shaders, with the name that was supplied.

Resource names can also be nested as demonstrated in the following code snippet:

INVESTIGO_RESOURCE_BEGIN("my special resource");

// ... any resources loaded here will be annotated with the name 'my special resource' ...

INVESTIGO_RESOURCE_BEGIN("textures");

// ... any resources loaded here will be annotated with the name 'my special resource/textures' ... 

INVESTIGO_RESOURCE_END;

INVESTIGO_RESOURCE_END;

The name of a particular resource can be retreived in code using the INVESTIGO_RESOURCE_NAME macro:

IDirect3DTexture9* someTexture = ...
const char* resourceName = INVESTIGO_RESOURCE_NAME(someTexture);

As with the other macros, the normal functionality of INVESTIGO_RESOURCE_NAME is conditionally compiled in when INVESTIGO_ENABLED is defined, otherwise it returns the string undefined. When INVESTIGO_ENABLED is defined but a resource is not named it returns the string unnamed.

Resources and their details are not yet viewable in the Investigo web UI, although this is a feature that will likely be added in the future.

Specifying names for resources makes it easier for you to identify DirectX resources that you may encounter while in the debugger. Given a pointer to a DirectX resource, say a texture for example, you can view the name of the resource in the debugger:



The INVESTIGO_RESOURCE_NAME macro is convenient and simplifies access to Investigo's Investigo::IResource interface. If you need full access to the interface you can manually extract it by calling QueryInterface.

IDirect3DTexture9* texture = ...
Investigo::IResource* investigoResource = NULL;

if (SUCCEEDED(texture->QueryInterface(__uuidof(Investigo::IResource), (void**) &investigoResource))
{
    string name = investigoResource->GetName();

    // ... do something with the resource ...

    // Release when finished so there is no memory leak.
    investigoResource->Release();
}

As already mentioned, make sure you wrap use of Investigo classes and functions with INVESTIGO_ENABLED. Also, it is very important that COM interfaces acquired through QueryInterface have their Release method called.

Investigo Application Interface

If the macros and inline functions aren't enough you can always load the Investigo DLL explicitly and manually extract the application interface. The application interface is a pure-virtual C++ class that directly exposes the Investigo singleton from the proxy DLL.

GetApplicationInterface is the DLL export function that retrieves a pointer to the Investigo interface, it is retrieved by a call to GetProcAddress.

HMODULE hInvestigo = LoadLibraryA("d3d9.dll");
if (!hInvestigo)
{
    // ... The DLL couldn't be loaded, handle this error gracefully ...
}

Investigo::pfnGetInterface getApplicationInterface = 
	(Investigo::pfnGetInterface) GetProcAddress(hInvestigo, "GetApplicationInterface");
if (!getApplicationInterface)
{
    // ... The DLL doesn't export the right function, handle this error gracefully ...
}

Again, remember to wrap your code with INVESTIGO_ENABLED and gracefully handle the case when the Investigo proxy DLL is not present.

Next, GetApplicationInterface is called to retreive the application interface:

Investigo::Interface* investigo = getApplicationInterface();

// ... you can now use investigo to interact with the Investigo proxy DLL ...

When finished with Investigo, the DLL should be unloaded:

FreeLibrary(hInvestigo);
hInvestigo = NULL;

As an alternative, if you want to use Investigo for the entire lifetime of the application and don't care about unloading the DLL, then simply call Investigo's EnsureLoaded function:

Investigo::Interface* investigo = Investigo::EnsureLoaded();

// ... you can now use investigo to interact with the Investigo proxy DLL ...

EnsureLoaded
lazily loads and caches the DLL handle and the application interface. Subsequent calls simple return the cached application interface pointer.

The macros and inline functions wrap and simplify the application interface and offer much of the same functionality. So you only need to use the application interface when you need lower-level or more fine-grained control.

See the API Reference section for more details on Investigo classes and functions.

Using Investigo for Automated Performance Testing

One of the big benefits of Investigo is the potential it offers for automated performance testing.

Let me give an example of automated performance testing:

  1. You execute a test script. I often like to write such scripts in Python.
  2. The script launches the DirectX application.
  3. Either via command line parameters or some other communication mechanism the script instructs the application to run a particular graphical test suite. When testing a game, for example, you might instruct it to load a particular level and have the AI play through a particular scenario (or cut-scene).
  4. Investigo performance logging is started at a particular point, either the application starts it directly via the Investigo API or it is sheduled to start via the Investigo config file.
  5. Performance logging continues until specified to stop, either by API or config file.
  6. The application exits, possibly forced to exit by Investigo (specified via config file or API), or is aborted more gracefully under application control.
  7. The test script now copies the captured performance data to a suitable location for storage.
  8. If there are more tests to do, loop back to step 2.
  9. When tests are complete ReportGen is used to generate performance reports for the test runs. Current test data is graphically compared against previous test data.
  10. The test script then emails the performance report to interested parties and archives the performance data.

In this manner performance testing can be part of a daily automated build process. By inspecting the resulting performance report and having a graphical comparison to historical data you can keep a daily handle (or weekly/monthly) on trends in application performance. It should also allow you to catch major performance issues more-or-less as soon as they are committed to the code base. And if you can make the test script clever enough it may even be possible to automatically detect when performance has degraded and even associate the potential performance problem with a particular change set in the version control repository. Combine this with Mercurial's bisect feature and you should be able to build a system that can autonomously search version control for the revision that introduced a particular performance issue.

Of course for all this to work accurately your application really must be deterministic and must execute the same way for each and every time run of the performance test suite. This can be especially hard to achieve in game development where multiple threads, CPU timers, random numbers and floating point arithmetic all conspire against you to destroy determinism. I believe however that determinism is worth fighing for as it drastically improves your ability to reproduce bugs and run automated tests.

Conclusion

This concludes the section of the article on using Investigo. Hopefully you can now use Investigo to help with your DirectX performance analysis and debugging.

If you need to get stuck into Investigo internals, want to help me add features to Investigo, or are just interested on how all this works, then please continue reading.

Please leave feedback and questions as messages on code project. Bugs and feature requests can also be logged via Investigo's SourceForge page.

Implementation

How does a proxy DLL work?

DirectX is implemented as a dynamic-link library, commonly known as a DLL. An application, such as a game, uses the DirectX DLL to communicate with the graphics hardware to render geometry.

Normally the DirectX DLL is loaded from the Windows system directory. The Windows DLL resolution rules makes it possible to replace a system DLL with a DLL of the same name that exists in the same directory as the application.



This means we can replace the DirectX DLL with out own proxy that pretends to be the DirectX DLL. The proxy DLL loads the original DLL and forwards all calls to it (hence it is called a proxy).

This puts us in a position to intercept all calls to exported DLL functions.

Proxy DLLs are also useful in other instances. One notable use and quite similar to Investigo is GLIntercept, a proxy and intercept tool for OpenGL.


When Proxy DLLs don't Work

It has to be noted that proxy DLLs don't work so easily in all circumstances.

I discovered this myself when I attempted to proxy the winsock API DLL. At the time I used it to debug and analyze the inputs and outputs of a network application. The winsock DLL is an example of a known DLL, a DLL that is protected (somewhat) by Windows. What this means is the system version of the DLL can't normally be overridden by a proxy DLL. This is, however, easy to circumvent by adding a registry entry that excludes the DLL from the set of known DLLs.

For winsock, ws_32.dll must be added to the following registry key:

HKLM\System\CurrentControlSet\Control\Session Manager\ExcludeFromKnownDlls

For more details see the Microsoft page on it.

For some reason d3d9.dll is not a known DLL. It probably would have made sense for Windows to place some kind of protection on this DLL to prevent proxying. But for reasons unknown, and to our advantage, d3d9.dll can be easily proxied without any modification to the registry.

Creating the Proxy DLL

Building the Initial Proxy DLL

So, how did I create the inital proxy DLL? The hard way to do it is to implement the DLL and all its export functions manually.

The smart way is to use wrappit by Michael Chourdakis. By using Michael's wrappit tool and following his instructions you can easily create a bare-bones proxy DLL that forwards all calls to the original DLL. After generation of the proxy DLL you need to ensure that the proxy is correctly loading the real system DLL. Once you have this working you should have a minimal working proxy DLL and this is a great time to do your first test and make sure that the DLL actually works with a real application. Seem easy? Don't get too comfortable there is still some work.

All the export functions in the generated proxy DLL are set to __declspec(naked). To usefully intercept calls you have to go through all the export functions and implement method signatures and calling conventions that are true to the original DLL. This is a time-consuming and error-prone part of the process. The header files or documentation that come with the DLL are usually necessary so that you know the return type and parameter types for each of the exported functions. In our case it is the DirectX header files and SDK documentation on MSDN that we need to look at.

So why is this part of the process so error-prone? The reason is that if the method signature is wrong for any particular function this will most likely cause some nasty problems such as memory corruptions and crashes. When you compile your proxy DLL with a method signature that is inconsistent with the real DLL you will likely trash the stack when calling that function and this can be a particularly painful problem to resolve, especially if you aren't sure which of the many method signatures is causing the problem. What is worse, and sometimes happens with bugs of this type, the problem doesn't always manifest itself immediately.

To reduce the risk of introducing problems the method signatures should be converted one at a time and each change throughly tested. If at any point you get a crash you will know that it was due to the previous function that you converted. Step by step, gradual, incremental and well-tested changes are a good programming practice in general, in this case it will definitely save you much frustration.

When analysing a DLL's exported functions or checking its dependencies, Dependency Walker, aka Depends.exe, is an invaluable tool that used to be included with Visual Studio.



At this point we have generated the initial DLL and implemented correct method signatures. However we still only have a dumb proxy DLL. This may be all you need for proxy simple DLLs that aid in debugging (like my winsock proxy), although for DirectX we still need to intercept the DirectX API functions and for this we must implement the DirectX COM interfaces.

Implementing the DirectX Interfaces

Now I will discuss creation of the proxy classes for each of DirectX interfaces. First, I copied the official DirectX interfaces into new header files in the Investigo project. I then converted each of the interfaces into a class that inherits and implements that particular interface.

The proxy classes are named after the original interfaces with Proxy prepended to the class name.

For example the proxy class for IDirect3DDevice9 is named ProxyIDirect3DDevice9.


Each proxy class has a data member called original that is a pointer to the real DirectX object:

class ProxyIDirect3D9 : public IDirect3D9
{
public:

    // ...

private:

    // ...

    IDirect3D9* original; // Pointer to real DirectX interface.
};
The proxy class constructor accepts a pointer to the real DirectX object and stores it in the member.
ProxyIDirect3D9::ProxyIDirect3D9(IDirect3D9 *_original) :
    original(_original) // Save pointer to real interface.
{
    // ...
}

The stored pointer is then used to forward the DirectX API calls, for example:

UINT __stdcall ProxyIDirect3D9::GetAdapterCount()
{
    return original->GetAdapterCount();
}

When creating a proxy DLL you only need to implement proxies for interfaces that you actually care about. For Investigo I have implemented all DirectX interfaces because I wanted Investigo to capture information about all DirectX API calls.


Proxy classes for resources are derived not just from the DirectX interface, but also from the Investigo resource interface and the Investigo resource base-class.

The example here is ProxyIDirect3DVertexShader9.

You can see in the diagram that it implements Investigo::IResource and derives from InvestigoResource. The former being the Investigo resource interface that is accessible via QueryInterface and the later being the resource base-class.

The base-class has functions to retrieve the real DirectX device, the resource ID and the application-defined resource name (when a name has been assigned).

This has been a brief overview of how I put the Investigo proxy DLL together. Let's move on now and look at some of the Investigo components in more detail.

Getting the Code

The code for Investigo can be found in InvestigoSrc.zip that is included with this article.
To get the latest code, please visit SourceForge.

If you have Mercurial installed you can clone a read-only copy of the respository:

hg clone http://hg.code.sf.net/p/investigo/src investigo-src

The included solution is for Visual Studio 2010.

Solution Walktrough

The Investigo solution has three projects.




ProxyDX is the project that builds the proxy DLL.

There is a also a minimal proxy DLL for D3DX, it can be used for debugging in Visual Studio, but otherwise it isn't particularly usable yet.

ReportGen is the project for the simple HTML performance report generator.

The ProxyDX project is where the DirectX proxy classes can be found. DllMain.cpp is one of the most important files here. It contains the DLL entry point and is where the DLL export functions are proxied. Two really important functions are defined in here.

The first is GetApplicationInterface, this is the DLL export function that retrieves Investigo's application interface, that is the interface that implements the Investigo API.

The second is the proxy implementation of Direct3DCreate9. This is where the proxy DirectX device is created and returned to the application.

Investigo Proxy DLL

The main Investigo class, that ties everything together is a singleton called InvestigoSingleton.

The core data structures in the proxy DLL are those that manage variables, pages and groups. The classes that implement these are: Variable, VariablePage and VariableGroup. The HistoryBuffer class manages the history for a single variable. VariableManager is a singleton class that manages all variables, pages and groups. Each of the classes for the core data structures knows how to format itself as JSON for delivery to the web UI.

The HttpServer class wraps the Mongoose HTTP server. DXHttpServer is a higher-level class that wraps HttpServer and is responsible for providing Investigo's DirectX specific services over the more general HTTP server. Numerous URLs are handled by DXHttpServer and as a response JSON data is dynamically generated and returned to the client. Other URLs are handled by HttpServer and are forwarded to DLLResourceManager, which statisfies requests by extracting and returning DLL embedded resources.

The PerformanceLog class handles output of performance data to CSV format log files.

In addition there are many classes that implement DirectX interfaces and we have looked at a couple of these already.

Investigo API

InvestigoSingleton directly implements the Investigo application interface Investigo::Interface.

GetApplicationInterface is a DLL export function that returns a pointer to the InvestigoSingleton via Investigo::Interface . An application can load the DLL, extract the application interface and then interact with the Investigo proxy DLL.

Simpler macros and inline functions have been layered over the application interface to make it more convenient to use.

Multi-threaded Data Access

Protecting data access across multiple threads has been an especially vital concern while implementing Investigo.

First, the DirectX rendering thread should not be blocked by any time consuming operations such as writing to the performance log files, etc. To do so would affect its performance and would defeat the purpose of profiling in the first place.

Second, the HTTP server is concurrent by default and uses multiple threads to serve the web UI.

Performance metrics generated by the rendering thread are buffered using a lock free queue. That is to say a queue that allows access by multiple threads without need of locks for synchronization. A separate thread is introduced, which I call the History Update Thread, that retreives the data from the lock-free queue and copies it to the per-variable history buffer. When performance logging is enabled the History Update Thread also outputs the performance log. The History Update Thread is controlled by the VariableManager singleton class.

As illustrated by the following diagram, a mutex is used to protect the data that is being modified by the History Update Thread and read by the HTTP Server Thread.



Investigo uses the Boost thread library which provides a convenient way of quickly getting started with multi-threaded programming, see these articles for more details:

http://antonym.org/2009/05/threading-with-boost---part-i-creating-threads.html
http://antonym.org/2010/01/threading-with-boost---part-ii-threading-challenges.html

Annotating DirectX API Calls


Investigo has a macro that is used to mark up each proxy DirectX API call.

For example, the proxy version of SetTexture:

HRESULT ProxyIDirect3DDevice9::SetTexture(DWORD Stage,IDirect3DBaseTexture9* pTexture)
{
    DX_RECORD_API_CALL(IDirect3DDevice9, SetTexture);

    // ...
}
The macro DX_RECORD_API_CALL registers a particular DirectX function with Investigo.

The function is added to the timing and metrics system. The duration of the call will be timed and the number of calls per-frame will be recorded. It also defines a location where a breakpoint can be triggered from the web UI.

The data for breakpoints is managed by InvestigoSingleton. The timing metrics and per-frame counts are stored in variables that are handled by VariableManager.

When a breakpoint is requested in the web UI the breakpoint location is recorded by InvestigoSingleton. When execution reaches that particular function a hard-coded breakpoint is triggered (using a call to DebugBreak).

Implementing the Web UI

The first question you are probably asking is why have a web UI in the first place?

The answer is simple. HTTP is convenient, easy to implement, well established, and thanks to Mongoose it is trivial to embed a HTTP server in a C++ application.

HTML, Javascript, CSS and image assets are served directly from embedded DLL resources, this means the web server, in this case the proxy DLL, can be distributed as an all-in-one package. After coming back to web application development following a long break I have been quite impressed at how sophisticated web technology is these days. Developing a small web app using jQuery and jQuery Mobile is pretty easy compared to the old days - at least after you conquer the learning curve.  Not to mention that the amount of resources, learning material and software, that is available is quite staggering.  It's not so much a case of is there anything good enough or do I roll my own code, now it's more of a choice between a dozen good APIs and the tough process of selecting the right one for the job!

One advantage of developing a web UI over a normal UI is that many problems are already solved. For example it is trivial to implement AJAX calls to retreive data using jQuery's AJAX functionality. Sockets would have been a more efficient communication mechanism (and a potential future choice for Investigo) but they are more difficult to use than AJAX (at least considering how simple jQuery makes AJAX).

There were many existing options for displaying the web UI's live performance graphs. I tried a few but I couldn't find a graphing API that had the real-time performance characteristics that I wanted. So I created my own simple graph renderer built on the HTML5 Canvas API. Still the performance of the graph rendering isn't the best and it isn't the most accurate way to evalute performance. It should be used only as an indication of performance. Serious performance work requires that performance metrics be recorded for offline analysis.

Another benefit of developing a web UI is that your iterative development is given a massive speed boost. Your app can be designed to run in an offline test mode and viewed in the browser directly from the filesystem.  Thus the turnaround time for the design / code / test cycle is vastly reduced.

Lastly I should mention that using a HTTP server and web app opens many doors. It will run on most platforms and it can be run remotely over the network.

Javascript Libraries

The web UI is built on jQuery and jQuery Mobile, both are indispensible Javascript libraries.  

jQuery provides browser independent DOM manipulation, events, AJAX and UI generation using templates.  It comes with a host of fantastic utility functions that you will come to rely upon.

jQuery Mobile means the web UI is usable on tablet devices. As an example use case, the DirectX application runs on a PC and the web UI can be open in front of you on a tablet.  This means you can run your application fullscreen and having the web UI running on seperate devices means it won't interfere with the fullscreen application.

jQuery Mobile makes it almost trival to build a decent looking multi-page, cross-browser and tablet-ready web app. It is amazing how much it does for you. The fact that you can build a decent UI without much CSS is very important to me. I'm mainly a developer and not much of a designer (although I like to dabble). jQuery Mobile is still pretty new and isn't without problems, but if you can keep your web app simple jQuery Mobile will make your life easier and I can only see it getting better and better.

Parts of the web UI are generated dynamically using jQuery templates that render JSON data to HTML. The data is injected into the templates and expanded to HTML then inserted into the DOM.

Layout with HTML and CSS can be rather painful for the non-web-designer. The first tool you need to arm yourself with when dealing with layout is a good grid layout system. I used jQuery Mobile 960 as it fits in nicely with jQuery Mobile and provides for your grid layout needs. Although I haven't yet had much occassion to use it, given Investigo's current simple layout requirements, although I have used it in various experimental features that might be part of Investigo in the future.

If I were to rewrite the web UI now, I'd probably want to investigate using an existing MVC framework instead of hand-rolling so much of the code that generates the UI from the data-model.  I recently found this article which might help you choose one of the many available libraries and frameworks.

Graph Rendering

The live performance graphs are rendered using the HTML5 Canvas API. The linked Wikipedia page has some simple examples of use. Use of the API in Investigo is quite straightforward, the main thing I do is to set the foreground color then call moveTo and lineTo to render the line. See Graph.js for the full code.

Offline Test Mode

The web UI has a special test mode that allows it to run in the browser directly from the filesystem. When using the test mode the HTTP server is not required. This means the web UI can be run locally for testing simply by double-clicking the main HTML file in Windows Explorer. Test mode uses faked JSON data to simulate the live enviroment with other resources (HTML, CSS and Javascript) loaded directly from the filesystem.  Not having to start and stop the HTTP server, allows rapid, iterative development of the web UI. It also makes reproduction and fixing of bugs considerably easier.

Test mode is automatically enabled when the web UI is run from the file system:

var testMode = false;

// ...

if (document.URL.startsWith("file://")) {
    console.log("Automatically entering test mode.");

    testMode = true;

}

Faked test data is included in the main HTML page (UI.html) in the normal way, for example:

<script type="text/javascript" src="test_config.js"></script>

The test data file assigns the test data to a global variable:

var test_config = [

    // ... test data defined here ...

]

When test mode is active all JSON data that would normally be retrieved from the HTTP server dynamically via AJAX is instead replaced with the test data The DataManager class (data_manager.js) is responsible for making the switch. For example, the function that retrieves DirectX configuration:

var get_config = function (success_callback) {
    if (testMode) {
        success_callback(test_config);
    } else {
        $.ajax({url: '/get_config',
            dataType: 'json',
            data: {},
            async: false,
            success: success_callback
            });
    }
};

Because the test data is included by the main HTML file, the browser attempts to load the test data even when the web UI is running in live mode when the test data is unnecessary. Normally the HTTP server would return an error for a request when the file doesn't exist. However the test data is handled specially by the HTTP server which responds with an empty file for any URL request that starts with test_.

Getting a Handle on Javascript

It has to be said, although buried in Javascript is a nice language, it is often obscured by the nastier aspects of the lanugage. Recommended reading is Javascript: The Good Parts to understand where to concentrate your learning effort and which areas of Javascript are best avoided.

Make sure you are using the following tools, they will save you much frustration when trying to resolve Javascript or JSON issues:

http://www.jslint.com/
http://www.jshint.com/
http://jsonlint.com/

Definitely make use of Chrome developer tools. I have also heard about great web developer plugins for Firefox. These tools allow Javascript debugging, give you a console, allows DOM inspection, provide performance tools and much more.

Implementing the HTTP Server

Investigo embeds the Mongoose HTTP server which delivers the web UI to the browser.

There isn't much documentation around to help with Mongoose setup, although fortunately it wasn't too difficult to figure out by trial and error.

Including the Mongoose Code

First, you need to include mongoose.c and mongoose.h in your project.

Running the Server

Investigo's HttpServer class is a wrapper for Mongoose, it is responsible for starting and stopping the server and managing URL callbacks.

The constructor starts the server:

HttpServer::HttpServer() :
    httpServerContext(NULL)
{
    const char *options[] = {

    // ... various Mongoose options ...
    };

    httpServerContext = mg_start(::HttpServerCallback, this, options);
}
Note the parameters to mg_start. Most important is the function that handles URL callbacks. Note also that the this pointer is passed in as the user data object.

The destructor stops the server:

HttpServer::~HttpServer()
{
    mg_stop(httpServerContext);
}

Handling URLs

Mongoose is implemented in C and a global function is required to handle URL callbacks. For example, Investigo's global URL handler:

static void* HttpServerCallback(enum mg_event event, struct mg_connection* conn, 
    const struct mg_request_info* request_info)
{
    HttpServer* httpServer = (HttpServer*)request_info->user_data;
    return httpServer->HttpServerCallback(event, conn, request_info);
}
Mongoose's user data object is cast back to HttpServer and the URL callback is then forwarded to a member function.

Generating Resources to Satisfy URL Requests

The HttpServerCallback member function is where the real work happens. It first dispatches specific URLs to specific handler functions:

void* HttpServer::HttpServerCallback(enum mg_event event, struct mg_connection* conn, const struct mg_request_info* request_info)
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... setup omitted ...

            // Dispatch to a handler for a specific URL.
            found->second(request_info->uri, ... other params ...);

            // ... generated response is written to the client ...

            return ""; // Don't need to do anything else.
        }

        // ...
    }

    // ...
}
The web UI's requests for JSON data are handled this way. DXHttpServer is a higher-level class that registers a number of URL handlers that generate JSON data. This is how the proxy DLL returns relevant DirectX data to the web UI. For example localhost:8080/get_config (try it in your browser, with Investigo running, and you will see the generated JSON data!) is routed to the function DXHttpServer::GetConfigCallback, which formats the DirectX configuration information as JSON.

The Mongoose functions mg_write and mg_printf are used to write the response to the client.

Serving Embedded DLL Resources

URLs that don't have a specific URL handler are dispatched to the DllResourceManager class.  When a URL matches a DLL embedded resources, that resource is extracted and served to the client:

void* HttpServer::HttpServerCallback(enum mg_event event, 
    struct mg_connection* conn, const struct mg_request_info* request_info)
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... Dispatch to a handler for a specific URL ...
        }

        // ... setup omitted ...

        if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
        {
            // Requested URL has been found in DLL resources.

            // .... serve the static DLL resource to the client ...

            return ""; // Content has been served.
        }

        // ...

    }

    return NULL; // Allow files to be loaded from the file system, but only from the directory specified in the configuration.
}
Serving the static parts of the web UI from DLL resources works well for those resources that don't need to be generated dynamically. The Investigo proxy DLL contains numerous such resources including the main page UI.html and the the Javascript behind it.

Test Data

The last kind of URL that is handled is those that begin with test_. These are the URLs that are used to load test data when the web UI is running in test mode. When running in live mode, i.e. served from the embedded HTTP server, rather than in test mode, these resources are unecessary and the HTTP server responds with an empty string.

This is almost too simple to need a code snippet, although I'll provide one anyway because this is a good chance to show mg_printf:

void* HttpServer::HttpServerCallback(enum mg_event event, 
    struct mg_connection* conn, const struct mg_request_info* request_info) 
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... Dispatch to a handler for a specific URL ...
        }

        // ... setup omitted ...

        if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
        {
            // .... serve the static DLL resource to the client ...
        }

        if (strncmp(request_info->uri, "/test_", 6) == 0 || // Does the URL begin with 'test_'?
        strncmp(request_info->uri, "test_", 5) == 0)
        {
            //
            // Any URL that is not found and begins with 'test_' is a test file that is 
            // unavailable when the web server is live.
            // Just return a blank document.
            //
            mg_printf(conn, "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/javascript\r\n\r\n");

            return "";
        }
    }

    // ...
}

Serving Resources from the Filesystem

Note the last line in the URL callback that returns NULL. This is a fallback for when a URL is requested that is not satisfied by any of the previous checks. When this happens we allow the resource to be served from the filesystem (only when a matching file exists). Serving files directly from the filesystem can help make development a little easier for resources under construction before we have packaged them as DLL resources.

Generating JSON to Satisfy AJAX Requests

JSON is the data format that delivers the dynamically generated performance data to the web UI.

Why JSON?

JSON is the defacto standard mechanism for passing data from server to web application. The JSON format is directly supported by jQuery's AJAX functions and is automatically translated from textual data into a Javascript object tree (provided the JSON is well formed).

On the C++ side JSON is generated via simple text manipulation. However the JSON generation code does get more complicated as your data requirements grow and also when you have heavily nested data structures. To allievate the confusion I went beyond simple text manipulation and created a higher-level C++ class called jsonstream to help in outputing JSON formatted data. jsonstream behaves much like a standard C++ stream object, although it's implementation is much simpler.

Generating Performance Reports

Included in the sample code is a C# console application that demonstrates generation of a HTML-based performance reports. This program is really just a simple example and not intended to be all things to all people. If you need to do something different with your performance reports I encourge you to dive-in and start changing the code.

Usage of ReportGen was explained previously. In this section I give a brief walkthough to help orientate you in the code.  

ReportGen takes the performance logs (aka CSV files) that are generated by Investigo and converts them into a HTML performance report. The actual data in the CSV files is converted to Javascript data files that are in the format required by the Google Charts API.   The CsvFile class is responsible for loading and converting the CSV files to Javascript.

The Main function can be found in Program.cs.  It calls down to GenerateReport to do most of the work extracting embedded resources and expanding HTML templates to create the performance report.  The Razor templating engine is used to generates the report's HTML pages. Normally Razor is used with ASP.NET web applications, although it is also usable embedded in an application to generate HTML pages from templates and data. The resources and templates required to generate the performance are stored as embedded resources. Use of embedded resources allows ReportGen to be an all-in-one package with no external dependencies. When the report is generated the templates are extracted from the resources, expanded using Razor and written to the filesystem.

Conclusion

This concludes my article introducing Investigo. Thanks for reading and I hope Investigo is useful to you.

If you want to help move Investigo forward please join me at SourceForge. If you like the article and the project please let your friends and colleagues know about it.

If you can't contribute directly, please consider making a donation that will help towards hardware and software costs. If you can't contribute much, just enough to buy a beer would be fantastic Wink | ;)

Future Features

There are many features that I would like to add to Investigo but haven't yet had the time. This just a small list, there are many more ideas.
  • DX11 support.
  • D3DX support.
  • Inspector (partially finished, allows the DX to be halted while you inspect render state, resources, allows single step of draw calls).
  • Frame analysis (what textures, shaders, etc were used in the current frame?)
  • Resource analysis (what textures, shaders, etc have been loaded since start up?)
  • Better offline performance viewer.
  • Performance experiments page (eg force all textures 2x2)
  • Conditional breakpoints based on render state.
  • Frame capture and replay (WebGL? NativeClient?)
  • Screenshots (partially finished)
  • Re-configure device state and reset.

Reference

Investigo Config File

The Investigo config file should be named Investigo.cfg and placed in the same directory as Investigo's DirectX proxy DLL d3d9.dll.

Name Description
DirectXPath


Set the path to the real DirectX dll, defaults to c:\Windows\System32\d3d9.dll.
OutputDirectory Set the output directory for Investigo performance logs.
PerformanceLoggingStartFrame The frame number where performance logging should be started, starting at 1.
PerformanceLoggingDuration The duration in frames until performance logging should be stopped.
ExitAfterPerformanceLogging Set to true to abort the application (by calling exit()) after performance
logging has stopped.
PortNo The port number to server the HTTP server on (defaults to 8080).
HistoryBufferSize Specifies the size of the variable history buffer (defaults to 1000).

API Reference

Files, types and namespaces

Name Type Description
InvestigoApplicationInterface.h


Header File Header file that contains the Investigo API that allows an application to integrate with Investigo.
Investigo
Investigo The namespace that contains Investigo classes and functions.
VariableType
Enum Specifies the type of an Investigo variable.
IVariable
Interface Interface to a variable.
IResource Interface Interface to Investigo resources (eg proxied DirectX resources).
Interface Interface Application interface to Investigo.
Used by the application to interact directly with Investigo.
You should prefer not to use this interface, convenient and simpler inline functions are defined below.
VariableHandle Class A handle to an Investigo variable.
TimingBlock Class Times a block of code, recording the elapsed time in a variable.

Macros

Name Description
INVESTIGO_INT_SET_VALUE(page, name, value)


Set the value of an investigo int variable.
INVESTIGO_INT_INCREMENT(page, name)
Increment the value of an investigo int variable.
INVESTIGO_INT_RESET(page, name) Reset the value of an investigo int variable.
INVESTIGO_DOUBLE_SET_VALUE(page, name, value) Set the value of an investigo double variable.
INVESTIGO_DOUBLE_RESET(page, name) Reset the value of an investigo double variable.
INVESTIGO_TIMER(page, name) Measure the execution duration of a block of code.
INVESTIGO_RESOURCE_NAME(iface) Retrieve the name that has been assigned to a DirectX resource.
INVESTIGO_RESOURCE_BEGIN(name) Start a block of code where loaded DirectX resources will be annotated with the specified name.
INVESTIGO_RESOURCE_END End a block of code where loaded DirectX resources will be annotated with the specified name.

Inline functions (Investigo namespace)

Name Description
Investigo::Interface* EnsureLoaded() Load the Investigo DLL and retrieve the application interface.
The interface is cached, subsequent calls simply return it.
void EnableDrawCalls(bool enable) Enable or disable draw calls.
void EnableDrawCalls() Enable draw calls.
void DisableDrawCalls() Disable draw calls.
void ResourceBegin(const char* resourceName) Begin a section of code that loads resources.
void ResourceEnd() End a section of code that loads resources.
std::string GetResourceName(IUnknown* iface) Get the name of a DirectX resource.
void TechniqueBegin(const char* techniqueName) Begin rendering of an effect technique.
void TechniqueEnd() End rendering of an effect technique.
void SetOutputDirectory(const char* outputDirectory) Set the directory that Investigo outputs log files to.
This must be set before performance logging is started.
bool IsPerformanceLoggingEnabled() Returns true if performance logging is currently enabled, or queued to start next frame.
void StartPerformanceLogging() Starts performance logging.
void StopPerformanceLogging() Stops performance logging.
int GetCurrentFrameNumber() Get the current frame number (starts at 1).
Returns 0 when Investigo is not loaded.
void StartPerformanceLoggingAt(int frameNumber) Starts performance logging at the specified frame number.
Has no effect if the specified frame has already passed.
void StopPerformanceLoggingAfter(int numFrames) Stops performance logging after X frames.
void ExitWhenPerformanceLoggingFinished() Causes the application to be forcefully exited when performance logging has finished.

Investigo::IVariable: Interface to a variable.

Name Description
void SetValue(int value)
Set the value of the variable as an int.
void Increment()
Increment the value of the variable (int's only).
void SetValue(double value)
Set the value of the variable as a double.
void Reset()
Reset the value of the variable.

Investigo::IResource: Interface to Investigo resources (eg proxied DirectX resources).

Name Description
int GetId() const Get the resource's unique ID.
const std::string& GetName() const Get the human readable name of the resource.

Investigo::Interface

Application interface to Investigo.
Used by the application to interact directly with Investigo.
You should prefer not to use this interface, convenient and simpler inline functions are defined below.

Name Description
int GetFrameNumber() const Get the current frame number, starting at 1.
double GetTime() const Get current time in seconds as measured by Investigo.
void SetOutputDirectory(const char* outputDirectory) Set the directory that Investigo outputs log files to.
This must be set before performance logging is started.
void EnableDrawCalls(bool enable) Enable/disable draw calls.
void BeginResource(const char* resourceName) Begin a section of code that loads a resource.
Resources that are loaded (eg textures and shaders) will inherit the specified resource name.
Calls can be nested.
void EndResource() End a section of code that loads a resource.
void BeginTechnique(const char* techinqueName) Begin a section of code that renders a technique.
Textures/shaders that are set can be associated with the technique (this is used by the
D3DX proxy). 
void EndTechnique() End a section of code that renders a technique.       
IVariable* GetVariable(const char* pageName,
    const char* variableName, VariableType variableType)
Retrieve a variable from a named page.
Variable and page are created if they doen't exist.
bool IsPerformanceLoggingEnabled() Returns true if performance logging is currently enabled, or queued to start next frame.
void StartPerformanceLogging() Starts performance logging.
void StopPerformanceLogging() Stops performance logging.
int GetCurrentFrameNumber() const Get the current frame number (starts at 1).
void StartPerformanceLoggingAt(int frameNumber) Starts performance logging at the specified frame number.
Has no effect if the specified frame has already passed.
void StopPerformanceLoggingAfter(int numFrames) Stops performance logging after X frames.
void ExitWhenPerformanceLoggingFinished() Causes the application to be forcefully exited when performance logging has finished.

Alternative Tools

There are a number of tools that are similar to Investigo or that have overlapping functionality.

Fraps
  • Good for measuring frames per second of DirectX applications.
  • It also does screenshots and videos for DirectX apps.

NVPerfHUD
  • Great for examining live performance of DirectX apps. U
  • Unfortunately it can only be used with NVidia cards and can't be used with arbitrary DirectX apps.
  • Unlike Investigo, it isn't integrated with Visual Studio and can't trigger breakpoints.

GPU PerfStudio
  • ATI's answer to NVPerfHUD, worth looking at, although I had trouble getting it to work for 32bit apps.

PIX
  • Great for understanding a simple scene, although for complex scenes there is some much data and it can be overwhelming.

GLIntercept
  • A fantastic open source OpenGL API interceptor.
  • This uses the same proxy DLL technique as Investigo.

DxExplorer
  • This looks similar to Investigo although I haven't tried it.
  • It doesn't appear to be open source.


Resources

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Ashley Davis
Software Developer (Senior)
Australia Australia
Ash is professional software developer living in Brisbane Australia. After many years in game development (and a few in finance) he is now a developer of serious games and simulations.
 
He is also a hobby developer when he can conjure the time.
 
Ash organizes a group in Brisbane called Game Technology Brisbane.
 
http://www.meetup.com/Game-Technology-Brisbane/
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmemberS.B.11-Mar-13 11:56 
Well done sir! You have covered many areas in great detail.
This work is a benefit not only to people interested in profiling their DirectX apps, but to anyone that wants to embed a web server in a native C++ app to enable a browser based control panel/dashboard.
GeneralMy vote of 4 PingroupYashfi13-Jan-13 19:25 
QuestionWell written Pinmemberdalstorp11-Nov-12 4:30 
AnswerRe: Well written PinmemberAshley Davis11-Nov-12 9:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 10 Nov 2012
Article Copyright 2012 by Ashley Davis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid