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
#endif
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.
{ INVESTIGO_TIMER("MyPage", "MyTimer");
}
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()
Investigo::Performance::StopLogging();
To enable and disable draw calls:
Investigo::Experiements::DisableDrawCalls();
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");
INVESTIGO_RESOURCE_BEGIN("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();
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)
{
}
Investigo::pfnGetInterface getApplicationInterface =
(Investigo::pfnGetInterface) GetProcAddress(hInvestigo, "GetApplicationInterface");
if (!getApplicationInterface)
{
}
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();
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();
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:
- You execute a test
script. I often like to write such scripts in Python.
- The script launches the DirectX application.
- 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).
- 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.
- Performance logging continues until specified to stop,
either by API
or config file.
- The application exits, possibly forced to exit by Investigo
(specified
via config file or API), or is aborted more gracefully under
application control.
- The test script now copies the captured performance data to
a
suitable location for storage.
- If there are more tests to do, loop back to step 2.
- 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.
- 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; };
The proxy class constructor accepts a pointer to the real DirectX
object and stores it in the member.
ProxyIDirect3D9::ProxyIDirect3D9(IDirect3D9 *_original) :
original(_original) {
}
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:
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 = [
]
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[] = {
};
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)
{
UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri);
if (found != urlCallbacks.end())
{
found->second(request_info->uri, ... other params ...);
return ""; }
}
}
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)
{
UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri);
if (found != urlCallbacks.end())
{
}
if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
{
return ""; }
}
return NULL; }
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)
{
UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri);
if (found != urlCallbacks.end())
{
}
if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
{
}
if (strncmp(request_info->uri, "/test_", 6) == 0 || strncmp(request_info->uri, "test_", 5) == 0)
{
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 ;)
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