COM: Component Object Model
COM: Component Object Model
Introduction
The beginnings of all things are weak and tender. We
must therefore be clear-sighted in beginnings, for, as in
their budding we discern not the danger, so in their full
growth we perceive not the remedy.
Michel de Montaigne: Essays, III, 1588
1.1
Overview
This book describes the Direct3D graphics pipeline, from presentation of scene
data to pixels appearing on the screen. The book is organized sequentially
following the data flow through the pipeline from the application to the image
displayed on the monitor. Each major section of the pipeline is treated by a
part of the book, with chapters and subsections detailing each discrete stage of
the pipeline. This section summarizes the contents of the book.
Part I begins with a review of basic concepts used in 3D computer graphics
and their representations in Direct3D. The IDirect3D9 interface is introduced
and device selection is described. The IDirect3DDevice9 interface is introduced
and an overview of device methods and internal state is given. Finally, a basic
framework is given for a 2D application.
Chapter 1 begins with an overview of the entire book. A review is given
of display technology and the important concept of gamma correction. The
representation of color in Direct3D and the macros for manipulating color values
are described. The relevant mathematics of vectors, geometry and matrices are
reviewed and summarized. A summary of COM and the IUnknown interface is COM: Component Object
given. Finally, the coding style conventions followed in this book are presented Model
along with some useful C++ coding techniques.
Chapter 2 describes the Direct3D object. Every application instantiates
this object to select a device from those available. Available devices advertise
their location in the Win32 virtual desktop and their capabilities to applications
3
CHAPTER 1. INTRODUCTION
through the Direct3D object. Selecting a device from those available and examining a devices capabilities are described. Multiple monitor considerations are
also discussed.
Chapter 3 describes the Direct3D device object which provides access to
the rendering pipeline. The device is the interface an application will use most
often and it has a large amount of internal state that controls every stage of the
rendering pipeline. This chapter provides a high-level overview of the device
and its associated internal state. Detailed discussion of the device state appears
throughout the rest of the book.
Chapter 4 describes the basic architecture of a typical Direct3D application.
Every 3D application can use 2D operations for manipulating frame buffer contents directly. An application can run in full-screen or windowed modes and the
differences are presented here. The handling of Windows messages and a basic display processing loop are presented. At times it may be convenient to use
GDI in a Direct3D application window and a method for mixing these two Windows subsystems is presented. Almost every full-screen application will want to
use the cursor management provided by the device. Device color palettes and
methods for gamma correction are presented.
Part II describes the geometry processing portion of the graphics pipeline.
The application delivers scene data to the pipeline in the form of geometric
primitives. The pipeline processes the geometric primitives through a series of
stages that results in pixels displayed on the monitor. This part describes the
start of the pipeline where the processing of geometry takes place.
Chapter 5 describes how to construct a scene representing the digital world
that is imaged by the imaginary camera of the device. A scene consists of a
collection of models drawn in sequence. Models are composed of a collection of
graphic primitives. Graphic primitives are composed from streams of vertex and
index data defining the shape and appearance of objects in the scene. Vertices
and indices are stored in resources created through the device.
Chapter 6 covers vertex transformations, vertex blending and user-defined
clipping planes. With transformations, primitives can be positioned relative to
each other in space. Vertex blending, also called skinning, allows for smooth
mesh interpolation. User-defined clipping planes can be used to provide cut
away views of primitives.
Chapter 7 covers viewing with a virtual camera and projection onto the
viewing plane which is displayed as pixels on the monitor. After modeling,
objects are positioned relative to a camera. Objects are then projected from 3D
camera space into the viewing plane for conversion into 2D screen pixels.
Chapter 8 describes the lighting of geometric primitives. The lighting model
is introduced and the supported shading algorithms and light types are described.
Chapter 9 covers programmable vertex shading. Programmable vertex shaders
can process the vertex data streams with custom code, producing a single vertex that is used for rasterization. The vertex shading machine architecture and
instruction set are presented.
Part III covers the rasterization portion of the pipeline where geometry is
1.1. OVERVIEW
CHAPTER 1. INTRODUCTION
Part V covers application level considerations. This part of the book describes issues that are important to applications but arent strictly part of the
graphics pipeline. Debugging strategies for applications are presented. Almost
all Direct3D applications will be concerned with performance; API related performance issues are discussed here. Finally, installation and deployment issues
for Direct3D applications are discussed.
Chapter 22 describes debugging strategies for Direct3D applications. This
includes using the debug run-time for DirectX 9.0c, techniques for debugging
full-screen applications and remote debugging.
Chapter 23 covers application performance considerations. All real devices
have limitations that affect performance. A general consideration of how the
pipeline state affects performance is given.
Chapter 24 covers application installation and setup.
Appendix A provides a guided tour of the DirectX SDK materials.
1.2
Display Technology
Display Adapter
Frame
Buffer
2D Pixel
Engine
Host CPU
Color Lookup
Table
D/A
Converter
CRT
Monitor
CHAPTER 1. INTRODUCTION
Rather than scan all three phosphor dots with a single electron gun, a CRT
will usually include three electron guns, one for each primary, scanning in parallel. A shadow mask placed between the picture tube surface and the electron
guns blocks the electron beams from illuminating phosphors other than those
corresponding to the currently addressed pixel.
LCDs, liquid crystal displays, are another common display technology. LCDs
are fundamentally a reflective or absorptive technology. Instead of phosphorescing chemicals that emit light when stimulated by an electron beam, LCDs contain cells of chemicals that absorb light so that only the desired frequencies of
light are reflected. So while a CRT has a phosphor that emits green, LCDs will
have a compound that absorbs all but green. Because LCDs dont emit light
by themselves they are generally accompanied by a backlight that controls the
overall brightness of the display. Because LCDs have evolved as replacements
for CRT monitors, they still go through a video scanout process.
So far we have described an ideal display that is perfectly addressable. An
ideal CRT would illuminate a single phosphor triplet perfectly with no other
phosphors receiving excitation from the electron beam. An ideal LCD would
respond linearly in brightness to its input signal. A real-world monitor does not
address an individual phosphor triplet directly, but excites a shadow-masked
swath as it traverses the screen. The phosphors are not perfect primary color
sources. The glass of the tube itself can scatter a phosphors emitted light. All
of these factors and others combine to distort the desired image scanned out
from the frame buffer. The next section discusses a way to compensate for this
distortion.
1.3
Gamma Correction
The intensity of light emitted from a monitor is not linear with respect to the
voltage applied to the monitor. If two voltages are applied with the second voltage being twice the first, the resulting second intensity will not necessarily be
twice the first intensity. Synthetic imagery assumes that colors can be manipulated linearly as additive primaries, requiring compensation for the non-linearity
of the monitor.
The non-linearity of a display is caused by many factors. The non-linearity
can be approximated by the equation I = kV relating the intensity I to the
applied voltage V . The term gamma correction derives from the use of the
Greek letter gamma in this equation.
A display may be characterized by a single value for . A more accurate
characterization accounts for the differences in the emission properties of the
phosphors and treats each primary separately, modeling each primarys response
with its own power law and value.
To ensure the best results from synthetic imagery, a monitor should be calibrated and its gamma measured by experiment. Poor compensation of gamma
can have a deleterious effect on antialiasing algorithms and cause imagery to
appear too dark or too washed out.
Gamma correction is most easily implemented as a color lookup table applied just before the data is presented to the DAC. Some display adapters do
not provide such a DAC and it must be incorporated into the applications
processing, at the expense of some color accuracy.1
10
CHAPTER 1. INTRODUCTION
on the same monitor, you may prefer to deal with gamma in a less intrusive
manner. Lack of hardware gamma ramps or driver support could also force you
to deal with gamma in another manner.
An application can take its linear color inputs and manually gamma correct
them, including textures and volumes, when they are loaded into the device.
This still leads to an inaccurate rendering since the device is performing operations on colors it assumes to be related linearly, but it is better than no
compensation for monitor gamma.
1.4
Color
We usually have an intuitive idea of what we mean by color. In computer graphics we must be quantitative and precise when we describe color to the computer.
In addition, there are factors due to the human visual system that must be considered when choosing color, as artists have long well known. The human visual
system can be considered the very last stage of the graphics pipeline after the
monitor produces an image.
A color is described to the computer as an ordered tuple of values from a
color space. The values themselves are called components and are coordinates in the color space. Windows GDI represents colors as an ordered tuple
of red, green and blue components with each component in the range [0.0, 1.0]
represented as an unsigned byte quantity in the range [0, 255].
By default Windows GDI uses the sRGB color space2 . This is an adopted
international standard color space that was first proposed by Hewlett-Packard
and Microsoft. However, the sRGB space is device-dependent owing to the
non-linearities of display systems.
Other color spaces provide device-independent color specifications. In 1931,
CIE: Commission Interna- the CIE created a device-independent standard color space CIEXYZ. There are
tional de lEclairage
variations of the CIE color space such as CIELUV, CIELAB, and others. This
color space can be used to ensure accurate reproduction of colors across a variety
of output technologies, such as matching screen output to printed output.
HLS: hue, lightness, satuIn computer graphics, it is often convenient to use the HLS and HSV color
ration
spaces for creating color ramps. If the intent of your application is to map data
HSV: hue, saturation, to a range of perceptibly equal colors, you will want to use a color space other
value
than sRGB. Equal increments in the components of an sRGB color do not give
rise to equal increments in perceived color change on the monitor.
A full treatment of color and its perception is beyond the scope of this book.
The mechanics of color matching and color space manipulation on Windows
can be found in the Platform SDK Help on Image Color Management (ICM).
References on the perceptual and artistic aspects of color matching and color
spaces can be found in the section 1.15.
2 http://www.color.org/sRGB.html
1.4. COLOR
11
Color in Direct3D
Direct3D uses the sRGB color space in windowed mode when GDI owns the
screen. In full-screen mode, an application can use a linear RGB color space
if gamma correction is available, otherwise the application will have to provide
gamma corrected source colors to Direct3D to achieve linear color response. The
linear RGB color space comes at the expense of some dynamic range when color
channels are 8 bits wide, but provides for the most accurate rendition of the
desired colors on the display.
Direct3D uses a variety of representations for colors depending on the context
in which they are used. When Direct3D accepts a parameter to the rendering
pipeline, those parameters are specified either as a single DWORD, or as a struct
of 4 floats. When interacting with image surface memory on a device, the colors
will be in a device-specific representation.
A struct of floating-point values is used in lighting and shading calculations
where accuracy is important. DWORDs are generally used in pixel related operations. Floating-point component values typically fall into the range [0.0, 1.0].3
When working with Win32 DIBs, 24-bit RGB colors packed in a DWORD sized DIB: device independent
quantity are common. Win32 provides COLORREF and PALETTEENTRY types for bitmap
representing colors, defined in <windef.h> and <wingdi.h>, respectively. For
more on manipulating color in Win32, see the MSDN Win32 documentation.
3 Certain special effects exploit the use of color in the pipeline with values outside this
range.
12
CHAPTER 1. INTRODUCTION
g, BYTE b);
b, BYTE a);
b);
g, float b, float a);
The D3DX utility library defines a D3DXCOLOR structure with member functions and support functions for color manipulation. It is described in chapter 15.
1.5
Basic Mathematics
Intervals
Intervals are convenient for describing ranges of values. A closed interval [n, m]
indicates the range of values x where n x m. The values n and m are the
endpoints of the interval. An open interval (n, m) indicates the range of values
x where n < x < m. A half-open interval [n, m) or (n, m] is equivalent to the
union of the open interval (n, m) and the n or m endpoint, respectively.
4 The macros are presented as if they were inline functions with prototypes, but no type
checking is performed with macros.
13
Points
In geometry, a point is represented by its coordinate in space. Geometry usually uses a Cartesian coordinate system, where the coordinates of a point in
space are represented by the distance along each of the principal axes to the
point. While other coordinate systems are used in geometry (such as spherical or cylindrical coordinate systems), 3D graphics uses a Cartesian coordinate
system. The dimensionality of a point corresponds to the number of coordinates needed to represent the point. Thus a two dimensional point requires two
Cartesian coordinates, and a three dimensional point requires three coordinates.
Floating-Point Values
In mathematics, all computations are exact and performing arithmetic on values
does not change their accuracy. However, computers store approximations to
real numbers as floating-point values and performing arithmetic operations can
cause their accuracy to change. Not all real numbers can be represented exactly
by a floating-point representation. Numbers such as and other transcendental
numbers have an infinite decimal expansion.
The approximate nature of floating-point numbers usually rears its head
when comparing floating point numbers and other structures composed of floatingpoint numbers, such as points, vectors, lines, planes, matrices, and so-on. The
14
CHAPTER 1. INTRODUCTION
variability in floating-point values can be dealt with in comparisons by considering two values equal when they are within of each other:
x y,
1.6
when|x y| <
Homogeneous Coordinates
1.7
Vectors
A vector has length and an orientation in space. Unlike a line, a vector does
not have a position in space. Two vectors are equal if they have equal length
and the same orientation. Vectors of length 1.0 are called unit vectors. A
normal vectors orientation is perpendicular to an objects surface, pointing
towards the outside of the surface. A normal vector is not necessarily a
unit vector. Vectors will be written with a lower case letter and an arrow
#
# #
accent: i , j , and k will denote unit vectors along the three principal axes in
a 3D coordinate system. A vectors scalar components are shown as hx, y, zi
or hv0 , v1 , v2 i. Vector arithmetic is summarized in table 1.1. Sometimes we
#
will want to construct a vector P Q whose direction points from P to Q with a
#
magnitude equal to the distance between the two points. The vector P Q can
be constructed from two points P and Q as hQx Px , Qy Py , Qz Pz i, or
#
more simply P Q = Q P .
#
#
a+ b
s #
a
#
kak
#
#
a b
15
= hax + bx , ay + by , az + bz i
= hsa
q x , say , saz i
= a2x + a2y + a2z
=
=
#
#
a b =
ax bx + ay by + az bz
#
k #
a k k b k cos
hay bz byaz , bxaz ax bz , ax by bx ay i
a az # ax az # ax ay
i +
= y
bx bz j + bx by
by bz
#
k
#
Table 1.1: Vector operations given two vectors #
a = hax , ay , az i and b =
hbx , by , bz i, with being the angle between them.
Direct3D represents 3D vectors using the D3DVECTOR structure. The D3DX
utility library also includes the 2D, 3D and 4D vector classes D3DXVECTOR2,
D3DXVECTOR3 and D3DXVECTOR4, respectively. The 3D vector class inherits from
D3DVECTOR. The D3DX vector classes provide member functions for vector construction, element access, and arithmetic operators. They are described in chapter 15.
typedef struct _D3DVECTOR
{
float x;
float y;
float z;
} D3DVECTOR;
1.8
Coordinate Systems
16
CHAPTER 1. INTRODUCTION
Y
Z
Y
Left-Handed
Right-Handed
Screen
1.9. MATRICES
Identity
Inverse
Transpose
Addition
Scalar Product
Vector Product
17
I
AA1
A1 A
AT
A+B
sA
#
vA
Matrix Product
=
=
=
=
=
=
M,
I
I
M,
M,
M,
= hm1 , . . . , mn i, where mi =
AB = M, where mij =
n
P
n
P
vk Aki
k=1
aik bkj
k=1
1.9
Matrices
Matrices map one coordinate system to another coordinate system and are said
to transform coordinates from one coordinate system to another. Transformation matrices in computer graphics are typically small, rectangular arrays of
floating-point values, with 2x2, 3x3 and 4x4 being the most commonly used
sizes of matrices. Direct3D declares a matrix as a 4x4 array of floats. The
first subscript increases from top to bottom along the matrix elements and the
second subscript increases from left to right along the matrix elements:
M=
m31 m32 m33 m34
m41 m42 m43 m44
Matrix arithmetic operations are summarized in table 1.2. Note that matrix
multiplication is not commutative, so that AB 6= BA for arbitrary A and B.
The former is referred to as post-multiplying B onto A and the latter is
referred to as pre-multiplying B onto A.
Sometimes the inverse of a matrix is needed in computer graphics. While
the procedure for obtaining the inverse of an arbitrary matrix is complex, the
matrices used in computer graphics are often affine transformations. Affine
transformations have the property that their inverse is their transpose, which is
trivial to compute.
Direct3D provides the D3DMATRIX structure for storing transformation matrices. D3DX provides a D3DXMATRIX class that inherits from D3DMATRIX providing basic mathematical operations as C++ operators and a set of functions
for operating on matrices. See chapter 15.
18
CHAPTER 1. INTRODUCTION
1
0.5
0
1
0.5
0
-0.5
-1 4
2
2
-0.5
2
-1 4
3
2
42
3 2
24
0
2 3
4
2
1
2
2
2 2
0.5
2
0 2
2
2
-0.5
2
2
2
-1 2
3 2
1.10
_13,
_23,
_33,
_43,
_14;
_24;
_34;
_44;
Aliasing
1.10. ALIASING
19
20
CHAPTER 1. INTRODUCTION
antialiased resampling
(a)
aliased resampling
(b)
1.11
Style Conventions
The following coding style conventions have been adopted throughout the example code used in this book for example code, Direct3D interfaces, macros,
functions and types.
Macros
Some C++ pre-processor macros are listed as if they were inline functions with
datatypes for input arguments and a return type where applicable. The macros
themselves dont enforce this usage so these datatypes are given only as a guide
to the reader as to the intended purpose of the macro.
Types
The Direct3D header files define an aliases for the Direct3D interface pointers.
For instance, a pointer to the interface IDirect3D9 has the typedef LPDIRECT3D9.
Rather than use the typedef, the interface name will be used in the text. Mixed
case names are easier to read and we wish to make it clear when a function takes
21
a pointer to a pointer by using the ** syntax. This book will also follow this
convention for any conventional Win32 structures or intrinsic C++ datatypes.
22
CHAPTER 1. INTRODUCTION
1.12
COM Objects
COM objects are binary components that are servers providing functionality to clients through interfaces. Direct3D is provided by Windows as a
collection of objects that act as servers for graphic operations requested by your
application as the client. Because COM provides a binary specification, the
components may be written in any language that supports the creation of COM
components. The COM runtime provides the infrastructure for locating specific
binary components on your machine and attaching them to your application
when you request their services. Binary components can be used from any programming environment that understands COM services, such as Visual Basic,
Visual C++ or Delphi Object Pascal.
A way is needed to specify a particular component as well as the particGUID: globally unique ular interface desired. COM uses a GUID to identify both components and
identifier
interfaces. GUIDs are 128-bit numbers generated by the algorithm described
in the Distributed Computing Environment specification and are guaranteed to
be unique across time and space.7
Components present interfaces as an immutable contract between the server
and the client. Once an interface is published to clients, it cannot be changed. If
new functionality is needed or an interface needs to be changed, a new interface
must be created. This ensures backward compatability at the interface level
with all existing clients of the component.
COM interfaces can be arranged in a hierarchy to extend existing interfaces,
but only single inheritence of interfaces is allowed. This does not turn out to be a
serious limitation because a COM object may provide any number of interfaces.
COM implements polymorphism in this manner.
COM objects are reference counted. Every time a client obtains an interface
on a COM object, the objects reference count is increased. Note that objects are
reference counted and not interfaces; an object supporting multiple interfaces
will have its reference count increased by one for each interface pointer obtained
by a client. When the client is finished with the interface, it releases it and
the objects reference count is decreased. When the objects reference count
decreases to zero, the object may be safely destroy itself. Just like memory
allocated on the heap, COM objects can be leaked if the interfaces are not
7 The DCE algorithm uses the physical network address of the machines netork adapter
for uniqueness in space. These identifiers are guaranteed to be unique. If no network adapter
is present when the GUID is generated, then the GUID is guaranteed only to be statistically
unique.
23
released. A leaked COM object should be treated like a memory leak and
eliminated during application development.
All COM interfaces inherit from IUnknown, a distinguished base interface
that manages the objects reference count and provides a means of obtaining any
other interface supported by the object. The IUnknown interface is summarized
in interface 1.1.
Interface 1.1: Summary of the IUnknown interface.
IUnknown
Methods
AddRef
QueryInterface
Release
interface IUnknown
{
HRESULT QueryInterface(REFIID iid,
void **result);
UINT AddRef();
UINT Release();
};
In C++, a COM object interface is presented to the application as a pure
virtual base class with only public methods. It is impossible to instantiate a
pure virtual base class, ensuring that an application can only obtain an interface
pointer from the COM runtime or a COM object interface method such as
QueryInterface. To invoke a method on a COM object, you obtain a pointer
to an interface supported by the object and then invoke the method like you
would any other C++ class instance pointer.
Microsoft follows the naming convention of the prefix I for the interface and
the prefix IID for the interface GUID. For instance, IUnknown is the symbol
name of the virtual base class for the interface. IID IUnknown is the symbol
name of the interfaces GUID.
Many COM methods, such as QueryInterface return the type HRESULT.
This is a DWORD sized quantity interpreted as three fields: a sucess bit, a facility
code and a status code. The sucess bit indicates the failure or success of the
operation as a whole. The facility code indicates the facility of origin in the
system. The status code provides extended information besides that indicated
in the success bit. Methods may succeed or fail with multiple distinct return
codes. Use the macros in table 1.3 to examine HRESULT return values.
In Direct3D, when you pass an interface pointer to a COM object, it calls
AddRef on the interface pointer if it stores the pointer as part of the objects
internal state. No AddRef call is made if the object uses the interface only for
24
CHAPTER 1. INTRODUCTION
Predicates
bool SUCCEEDED(HRESULT hr)
bool FAILED(HRESULT hr)
Accessors
DWORD HRESULT FACILITY(HRESULT hr)
DWORD HRESULT CODE(HRESULT hr)
#define WIN32_LEAN_AND_MEAN
#define STRICT
#include <windows.h>
#include <dshow.h>
int APIENTRY
WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
return -1;
}
// create the filter graph manager
IGraphBuilder *graph = 0;
hr = ::CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_ALL, IID_IGraphBuilder,
reinterpret_cast<void **>(&graph));
if (FAILED(hr))
{
::CoUninitialize();
return -1;
}
// build the graph
hr = graph->RenderFile(L"CLOCKTXT.AVI", NULL);
if (FAILED(hr))
{
graph->Release();
::CoUninitialize();
return -1;
}
// run the graph
{
IMediaControl *control = 0;
hr = graph->QueryInterface(IID_IMediaControl,
reinterpret_cast<void **>(&control));
if (FAILED(hr))
{
graph->Release();
::CoUninitialize();
return -1;
25
26
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
CHAPTER 1. INTRODUCTION
}
hr = control->Run();
if (FAILED(hr))
{
control->Release();
graph->Release();
::CoUninitialize();
return -1;
}
control->Release();
}
// wait for the AVI to complete
{
long event_code = 0;
IMediaEvent *event = 0;
hr = graph->QueryInterface(IID_IMediaEvent,
reinterpret_cast<void **>(&event));
if (FAILED(hr))
{
graph->Release();
::CoUninitialize();
return -1;
}
hr = event->WaitForCompletion(INFINITE,
&event_code);
if (FAILED(hr))
{
event->Release();
graph->Release();
::CoUninitialize();
return -1;
}
event->Release();
}
graph->Release();
::CoUninitialize();
return 0;
}
1.13
27
Code Techniques
This section presents some code techniques you may find helpful during development of your Direct3D C++ application.
28
CHAPTER 1. INTRODUCTION
iface->Release();
return hr;
}
hr = DoSomethingElse(iface);
if (FAILED(hr))
{
iface->Release();
return hr;
}
iface->Release();
return S_OK;
}
Writing out if tests for every method can be tedious and error-prone. The
readability of the program suffers as every function is littered with uncommon
case error handling code and the commonly taken successful flow of control is
obscured. Additionally as we allocate temporary resources through the course
of the function, each must be released on an unexpected failure. Wether you
repeat the cleanup code, as in listing 1.1, or use a goto with multiple labels for
various amounts of cleanup at the end of the routine, the style is awkward and
error-prone.
C++ provides the throw statement for triggering an exceptional error condition and a catch statement for handling an exception. When an exception
is thrown, an object is constructed to identify the exception. Then the call
stack is unwound one stack frame at a time to find an exception handler. As
each stack frame is unwound, all objects that were created on the stack have
their destructors called. When an matching exception handler is found, control
is transferred to the exception handler and continues normally. If no matching
handler if found on the call stack, then the C++ runtime will abort the programs
execution.
C++ exceptions and resource acquisition classes localize error handling code
to one place and improve the readability of the code. A resource acquisition class
is a class that acquires some sort of resource in its constructor and releases the
resource in its destructor. The resource can be temporarily allocated memory,
COM interface pointers, mutual exclusion locks, open file handles, etc. Acquiring such resources with helper classes allocated on the stack ensures that the
resources are properly released, even when an exception is thrown. When the
call occurs normally, no exception is thrown and the helper objects destructor
is called when the object goes out of scope. Either way, the resource is properly
released when it is no longer needed.
To map HRESULTs to C++ exceptions, we need an exception class, an inline
function and a preprocessor macro. First, a class hr message is provided that
encapsulates the error state of an HRESULT, a source code filename and a line
number. Next, an inline function is provided that examines an HRESULT and
throws an exception of type hr message if the HRESULT indicates failure. If the
29
HRESULT indicates success, the inline function returns its HRESULT argument.
Finally, a preprocessor macro is provided that automatically fills in the filename
and line arguments when invoked. The complete header file is given in listing 1.2.
Listing 1.2: <rt/hr.h>: mapping HRESULTs to exceptions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#if !defined(RT_HR_H)
#define RT_HR_H
// hr.h
//
// Description: Handling unexpected COM HRESULTs as C++
// exceptions.
//
// The utilities provided here aid in debugging (and
// logging after delivery) as they record the source
// file/line location where the error was encountered.
//
// Setting breakpoints on the throw statemens in this
// header file will stop execution for all errors
// encountered by the program through THR(), TWS(), etc.,
// before the exception is thrown, allowing you to check
// the call stack for the source of the error in the
// debugger.
//
// This file is meant to be used to trap unexepected
// errors. For expected error conditions (such as the
// HRESULTs returned by IDirect3D9::CheckDeviceType, or
// the D3DERR_DEVICELOST result returned by
// IDirect3DDevice9::Present), you should explicitly test
// against expected failure codes and only THR() on error
// code you do not expect.
//
// This will avoid the cost of exceptions for the normal
// flow of control, and the overhead of exceptions is
// perfectly reasonable when you expect the call to succeed
// but it fails anyway. These failures usually represent
// an invalid parameter passed to Direct3D and the returned
// HRESULT will be D3DERR_INVALIDCALL with coresponding
// additional information in the debug output.
//
// Provides:
// hr_message
//
Exception class that records a failed HRESULT,
//
file/line source file pair indicating where the
//
failed HRESULT was generated, and possible context
//
message. Its constructor (not inlined) looks up
30
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
CHAPTER 1. INTRODUCTION
//
the HRESULT via FormatMessage() and a few other
//
places specific to DirectX to generate the message
//
text.
//
// display_error
//
Displays a message box containing the error string
//
inside an hr_message and returns the HRESULT.
//
// throw_hr, throw_win functions
//
Inline function for checking a result and throwing
//
an exception of type hr_message upon failure.
//
// THR(), TWS() and variants
//
Macros to supply __FILE__ and __LINE__ at to
//
throw_hr/throw_win so that the file/line is
//
recorded from the source including this header and
//
not this header itself.
//
// Example:
//
try
//
{
//
THR(some_interface_ptr->SomeMethod());
//
HFONT font = TWS(::CreateFontIndirect(&lf));
//
// other stuff that may throw rt::hr_message
//
}
//
catch (const rt::hr_message &bang)
//
{
//
return rt::display_error(bang);
//
}
//
// Alternatively, if RT_NO_THROW is defined when this file
// is processed, the exception object is constructed to obtain
// error message text, writes this text to the debug stream
// and then ATLASSERT(false) is called. This allows <rt/hr.h>
// to be easily used within the sample SDK framework which
// does not use exceptions for error handling.
//
// Copyright (C) 2000-2006, Richard Thomson, all rights reserved.
//
#include <windows.h>
#include <tchar.h>
#if defined(RT_NO_THROW)
#include <atlbase.h>
#endif
31
namespace rt
{
/////////////////////////////////////////////////////////
// hr_message
//
// Class for bundling up an HRESULT and a message and a
// source code file/line number.
//
class hr_message
{
public:
hr_message(const TCHAR *file, unsigned line,
HRESULT hr = E_FAIL,
const TCHAR *message = NULL);
~hr_message() {}
const TCHAR *file() const
unsigned line() const
HRESULT result() const
const TCHAR *message() const
{
{
{
{
return
return
return
return
m_file; }
m_line; }
m_result; }
m_message; }
private:
enum
{
MESSAGE_LEN = 1024
};
const TCHAR *m_file;
unsigned m_line;
HRESULT m_result;
TCHAR m_message[MESSAGE_LEN];
};
/////////////////////////////////////////////////////////
// throw_hr
//
// Function that throws an exception when the given
// HRESULT failed.
//
inline HRESULT
throw_hr(const TCHAR *file, unsigned line,
HRESULT hr, const TCHAR *message = NULL)
{
if (FAILED(hr))
{
#if defined(RT_NO_THROW)
hr_message bang(file, line, hr, message);
32
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
CHAPTER 1. INTRODUCTION
::OutputDebugString(bang.message());
ATLASSERT(false);
#else
throw hr_message(file, line, hr, message);
#endif
}
return hr;
}
/////////////////////////////////////////////////////////
// throw_win
//
// Function that throws an exception when a Win32 return
// value indicates failure.
//
template<typename T>
inline T
throw_win(const TCHAR *file, unsigned line,
T status, const TCHAR *message = NULL,
int error = GetLastError())
{
if (!status)
{
throw_hr(file, line, HRESULT_FROM_WIN32(error),
message);
}
return status;
}
/////////////////////////////////////////////////////////
// display_error
//
// Takes an hr_message and displays the message string in
// a message box and returns the HRESULT value.
//
inline HRESULT
display_error(const hr_message &bang,
const TCHAR *title = NULL)
{
::MessageBox(0, bang.message(), title, 0);
return bang.result();
}
};
// macros to fill in __FILE__, __LINE__ and _T() automatically
33
Having localized the error handling with these tools, we can transform our
hypothetical sample into the following:
class some_ptr
{
public:
34
CHAPTER 1. INTRODUCTION
some_ptr(ISomeInterface *some) : m_some(some) {}
~some_ptr() { m_some->Release(); }
operator ISomeInterface *() const { return m_some; }
private:
ISomeInterface *m_some;
};
void
DoSomething(ISomeInterface *some)
{
THR(some->Method1());
THR(some->Method2());
THR(some->Method3());
}
bool
DoThings()
{
try
{
some_ptr some(GetInterface());
DoSomething(some);
DoSomethingElse(some);
}
catch (const rt::hr_message &bang)
{
// log unexpected error here
return false;
}
return true;
}
This is admittedly a contrived example, but the main flow of control is now
clearly visible, at the expense of wrapping each method in an invocation of
the THR macro and the entire function in a try/catch block. Note also we had
to wrap the ISomeInterface in a smart pointer so that it would be properly
released in case DoSomething or DoSomethingElse threw an exception.
A more realistic example would place the try/catch at an outer scope, where
error context information would be logged for debugging or displayed with a
dialog box. This is especially true if DoThings is called from a tight inner loop.
Another advantage of localizing the code for error handling is that a breakpoint can be set on the throw statement in throw hr. When an error is encountered that would throw an exception, the breakpoint is activated and the
programmer can examine the call stack and program state at the exact location
35
Smart Pointers
ATL 3.0 provides two helper template classes, CComPtr<> and CComQIPtr<> that
simplify the management of COM interface pointers. CComPtr<> is used when
you want to obtain an interface pointer via ::CoCreateInstance. CComQIPtr<>
is used when you want to obtain an interface pointer via QueryInterface. These
classes are located in the <atlbase.h> header file in the ATL includes directory.
The C++ standard library helper class std::auto ptr, declared in <memory>,
can be used to avoid leaks of dynamically allocated memory. You should read
and understand the implementation in <memory> for auto ptr, or <atlbase.h>
for CComPtr<> and CComQIPtr<>. Misunderstandings of the pointer ownership
36
CHAPTER 1. INTRODUCTION
policies for smart pointer classes can lead to bugs just as difficult to track down
as those that spurred the development of smart pointer classes in the first place.
The std::auto ptr class can only be used with a single object as it uses the
scalar memory allocator new instead of the array allocator new[]. For a smart
array pointer, see <http://www.boost.org/>.
You can also write your own smart pointer classes. For instance, the C++
standard library doesnt provide functions for reference-counted pointers to heap
memory the way COM objects are reference counted. You can also incrementally extend existing smart pointer classes. You can also write your own smart
resource helper classes for other dynamic resources such as open file handles,
critical sections, mutexes, GDI objects, etc.
#define WIN32_LEAN_AND_MEAN
#define STRICT
#include <windows.h>
#include <atlbase.h>
#include <dshow.h>
#include "rt/hr.h"
// acquire COM runtime as a resource
class com_runtime
{
public:
com_runtime() { THR(::CoInitialize(NULL)); }
~com_runtime() { ::CoUninitialize(); }
};
// extend CComQIPtr<T> to throw on no interface
template <typename T>
class com_qi_ptr : public CComQIPtr<T>
{
public:
com_qi_ptr(IUnknown *base) : CComQIPtr<T>(base)
{
if (!p) THR(E_NOINTERFACE);
}
~com_qi_ptr() {}
37
};
int APIENTRY
WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
com_runtime com;
// Create the filter graph manager
CComPtr<IGraphBuilder> graph;
THR(graph.CoCreateInstance(CLSID_FilterGraph));
// Build the graph.
THR(graph->RenderFile(L"CLOCKTXT.AVI", NULL));
// Run the graph.
THR(com_qi_ptr<IMediaControl>(graph)->Run());
// Wait for completion.
long event_code = 0;
THR(com_qi_ptr<IMediaEvent>(graph)->
WaitForCompletion(INFINITE, &event_code));
}
catch (const rt::hr_message &bang)
{
return rt::display_error(bang);
}
return 0;
}
1.14
Graphics hardware has been evolving at a tremendous rate, with raw graphics
performance doubling almost every nine months. The Direct3D API has been
evolving as well, to stay in step with the evolution of the hardware. This book
describes the Direct3D API as shipped with the DirectX 9.0c SDK (June 2006).
1.14.1
DirectX 1-2
The first two versions of DirectX did not include any 3D interfaces. DirectDraw
provided interfaces for two-dimensional pixel manipulation only.
38
1.14.2
CHAPTER 1. INTRODUCTION
DirectX 3
This was the first version of DirectX to include the Direct3D API with interfaces
for enumerating and obtaining a device, the device itself and interfaces for execute buffers, lights, materials, viewports and textures. The Direct3D interfaces
in this version of DirectX did not have any numeral suffix to their name, such
as IDirect3DDevice. If your program must operate in Windows NT 4, then
you are restricted to using this version of DirectX, but it is difficult to use and
lacks many of the features provided by modern 3D hardware.
The pipeline was presented with data by constructing execute buffers, in
which the application stored an interleaved stream of command and data tokens.
Retained mode provided scene graph capabilities to applications at a high
level of abstraction.
The facilities of DirectDraw were utilized for performing two-dimensional operations and operations involving pixel data. Direct3D would remain associated
with DirectDraw for several more revisions of DirectX.
1.14.3
DirectX 5
The next release of DirectX was version 5, with DirectX having skipped a version
4 release. This was the first version of Direct3D to introduce the ability to
specify primitives directly to methods on the device, instead of using execute
buffers. Direct3D computed the interleaved command and data token stream
from the arguments to methods on the device. This immediate-mode was an
alternative to retained-mode and gave applications more freedom in deciding
which objects and structures would be used to represent their scenes. The
interfaces were all suffixed with the number 2 to indicate the second version of
Direct3D.
1.14.4
DirectX 6
DirectX 6 was the first release of Direct3D to provide multitexturing, bump mapping, automatic texture management, flexible vertex formats, vertex buffers, a
w-based depth buffer, and stencil buffers. Vertex transformation and processing
was still performed exclusive only the CPU.
1.14.5
DirectX 7
The era of hardware transformation and lighting began with DirectX 7. DirectX 7 is also the last version of DirectX that contained a software rasterizer.
As hardware acceleration became more commonplace, the need for a software
rasterizer was reduced. This version of Direct3D added support for hardware
transformation and lighting, cubic environment mapping, geometry blending,
device state blocks and an API for Visual Basic 6.
This was the first version of Direct3D to include the D3DX utility library.
The library provided a simplified device management code path through a con-
39
text object, a matrix stack object for managing coordinate frame hierarchies
and a simple shape object.
In DirectX 7, the interface numbering was synchronized to the version of
DirectX in which the interfaces were shipped, so all interfaces were suffixed with
the number 7. This practice has continued throughout all subsequent releases
of Direct3D.
1.14.6
DirectX 8.0
As graphics hardware evolved and became more complex, the explosion of combinations of a fixed-function model became unwieldy. DirectX 8 was the first
release to provide support for vertex and pixel shaders that allow arbitrary programs to execute per-vertex or per-pixel with vertex shader model 1.1 and pixel
shader model 1.1 and 1.2. The interfaces were simplified and DirectDraw functionality was incorporated into the Direct3D interfaces, eliminating the need for
DirectDraw. DirectX 8 added support for point sprites, multisampling, cursor
support, and volume textures.
1.14.7
DirectX 8.1
DirectX 8.1 was not a major overhaul of the interfaces as in previous versions.
This was the first release that would extend the API solely through minor fixedfunction pipeline additions and significant enhancements through new shader
models for pixel shaders. Support was added for pixel shader models 1.2, 1.3
and 1.4. The SDK was expanded through enhancements to the D3DX library.
1.14.8
The second generation of shader capable hardware supported more complex and
longer vertex and pixel shaders. DirectX 9 added vertex shader model 2.0 and
3.0, pixel shader model 2.0 and 3.0, separated vertex declarations from vertex
shaders, hardware mipmap generation, samplers, adaptive tessellation, asynchronous notifications, sRGB textures, multielement textures, multiple render
targets, multihead display support, scissor test, two-sided stencil buffers, displacement maps, line antialiasing, and sphere mapped texture coordinate generation.
The enhancements to D3DX utility library provided the high-level shader
language, precomputed radiance transfer, and tone mapping, extended the effects framework for enhanced animation, and extended text and sprite handling.
The core runtime introduced in DirectX 9 contained significant enhancements to the shader models, beyond of what was capable at the time DirectX 9
was released. The intention was for future releases to update the runtime less
frequently and provide new features through the D3DX utility library and new
shader profiles targeting specific implementations of the shader models introduced with DirectX 9. The name of each SDK release gives the version of the
40
CHAPTER 1. INTRODUCTION
runtime and the month in which the SDK was released. This book discusses
the June 2006 release of the SDK for the DirectX 9.0c version of the runtime.
1.15
Further Reading
Computer graphics has become such a diverse field that it is impossible to cover
all aspects of the field in a single book. The following list provides more detailed
references for some of the subjects discussed in this chapter.
The C++ Programming Language, 3rd edition, by Bjarne Stroustrup.
Comprehensive explanation of all features of the C++ language by its
creator. Includes C++ exceptions, the std::auto ptr<> template class,
and helper classes for use with exceptions. Errata are located on-line at
hhttp://www.research.att.com/bs/3rd.htmli.
The C++ Standard Library: A Tutorial and Handbook, by Nicolai M. Josuttis.
An excellent reference to the entire standard library covering the Standard
Template Library, the iostreams library and other aspects of the C++
standard library. Graphics applications often need dynamically resizable
arrays, linked lists and other data structures and algorithms provided by
the standard library.
Discrete Time Signal Processing, by Alan V. Oppenheim, Ronald W. Schafer,
John R. Buck
Updated classic work on digital signal processing, with many overlapping
areas for computer graphics. Contains the theory of sampling, aliasing,
and reconstruction of continuous signals and much more.
Essential COM, by Don Box.
Provides an excellent introduction to COM and an explanation of the
technology from both client and server points of view.
Graphics Gems, edited by Ronen Barzel, founding editor Andrew S. Glassner.
Provides many useful and practical algorithms for solving small problems
in graphics. After five volumes, the book series has been replaced by
the Journal of Graphics Tools. The books source code is available at
hftp://graphics.stanford.edu/pub/Graphics/GraphicsGems/i.
Illumination and Color in Computer Generated Imagery, by Roy Hall.
Extensive coverage of color and its use in computer synthetic imagery.
Inside OLE, 2nd ed., by Kraig Brockschmidt.
One of the first books that explains all of COM (despite the title referring
to OLE) both from a client and a servers point of view. The entire text
is available through MSDN.
41
hhttp:
42
CHAPTER 1. INTRODUCTION