ConfigSystem Sample
Description
The sample showcases a configuration system using a database of hardware device
models. The sample detects the hardware on the system, and then looks up the
database to find custom behavior defined for those existing devices.
|
Path
Source: |
DX90SDK\Samples\C++\Direct3D\ConfigSystem |
Executable: |
DX90SDK\Samples\C++\Direct3D\bin\ConfigSystem.exe |
Sample Overview
The sample is a demonstration of a database-driven application configuration
system. Today, several shipping games, such as Halo PC, Microsoft Flight
Simulator, Microsoft Combat Flight Simulator, and Rise of Nations are already
taking advantage of this versatile and powerful system, and this sample will
explain why. 3D Graphic cards have a set of capabilities that they support to make
better-looking images. Realistically, not every card supports the exact same
set of capabilities. Therefore, applications typically need to check the
existence of support for the capabilities that they take advantage of. In
Direct3D, device capabilities are reported in the capability structure, or cap
bits for short, that can be obtained by calling the device's GetDeviceCaps()
method. The cap bits structure details what the device can and cannot support.
Usually, the cap bits provide a fast and easy way to know what rendering
features an application can utilize.
One may logically wonder that if the cap bits are easy and simple, why is there
a need to use a database system ever? The reason is because the cap bits do not
speak of everything. Devices may mistakenly report cap bits which they actually
do not support, or which are broken. Furthermore, a capability may be enabled
at the expense of severe performance penalty. In this case, many developers
prefer to have a steady performance over a particular feature. To overcome
these issues associated with the cap bits, an application developer could test
all supported hardware models during development, then in a database, record a
list of hardware that the application has trouble with, as well as the specific
problems each device has. Then at run time, the application detects the
hardware that the host system has and looks up the hardware in its database. If
the hardware model is found, the database provides the detail as to what
special actions the application must take to ensure a smooth run.
How does the sample work?
The sample demonstrates the database-driven configuration system with three
code components:
-
Main.cpp - represents the configuration system client. It makes use of
the system as well as handling necessary sample code such as rendering and
animation routines.
-
ConfigDatabase.cpp - implements the configuration database as a COM
object. The database object handles parsing of the configuration file.
-
ConfigManager.cpp - a component between the database and its client. It
encapsulates access to the database and provides efficient access to the
properties.
When the sample is run, the user is in a cell with several balls on the ground.
The user can shoot out additional balls and watch them bounce around. This
scene will represent a hypothetically-exciting game. At the bottom of the
screen, a list of properties will be listed. These properties are parsed and
loaded from the configuration database file based on the detected display and
audio devices that the sample detects. The existence of these properties
affects how the sample runs (for instance, how it renders or handles sound).
The Database
In the sample, the configuration database file is a text file. Each line is
either a statement or keyword that affects parsing, a property definition, or
a comment that starts with "//". When parsing the database,
the text letter case is ignored. The file contains three sections.
The first section lists the hardware requirement of the sample. It starts
with a statement with a single word "Requirements" followed by one or more
property/value assignments. Finally, at the end of the section, a single
statement "break" indicates that the section ends. Below is an example
of requirement sets in the configuration file:
Requirements
OS=Win98
CpuSpeed=733
Memory=128
VideoMemory=64
DirectX=4.9.0.902
DiskSpace=100
break
|
The requirements are explained below:
Requirement Property |
Description |
OS |
Operating system version. Supported values, in ascending order, are: Win95, Win98,
WinME, Win2k, WinXP, and Win2003.
|
CpuSpeed |
Processor speed in mega-hertz. |
Memory |
Amount of system memory in megabytes.
|
VideoMemory |
Amount of video memory in megabytes.
|
DirectX |
DirectX version on the system in this format: #.#.#.#
|
Diskspace |
Amount of available disk space in megabytes. This requirement is here for illustration
purpose only and is not checked by the sample. |
The second section is a list of named property sets. A property set is
a set of custom properties followed by a "break" line. The syntax of a named property
set is shown below:
PropertySet = "MyPropSet"
CustomPropertyName1 // Name
CustomPropertyName2 = Value // Name/Value
break
|
The property set begins with the keyword "PropertySet", followed by = then by
the name of the property set enclosed in quotes. In the above example,
MyPropSet has two custom properties, PropertyName1 and PropertyName2.
A custom property is a name/value pair, or simply a name by itself. It is defined
and applied to devices by the application and it serves as a flag to signal the application
that a special action should be taken when dealing with the devices that this
custom property is applied to.
A property set can refer to other property sets, by using the following syntax:
PropertySet = "SmallPropSet"
CustomPropertyName1 // Name
CustomPropertyName2 = Value // Name/Value
break
PropertySet = "BigPropSet"
PropertySet = "SmallPropSet" // BigPropSet includes SmallPropSet's properties
CustomPropertyName3 // Name
CustomPropertyName4 = Value // Name/Value
break
|
The keyword PropertySet is used in place of a custom property name,
and its value is the name of the property set to include, in quotes. In the
above example, BigPropSet has 4 custom properties total, as if it were defined
like this:
PropertySet = "BigPropSet"
CustomPropertyName1 // Name
CustomPropertyName2 = Value // Name/Value
CustomPropertyName3 // Name
CustomPropertyName4 = Value // Name/Value
break
|
Property sets provide a way to group related custom properties under one name.
These properties can then be applied all at once, eliminating the need to
list them all out one by one.
The third section lists the hardware device entries included in the database.
In the sample, the database contains audio and display devices.
The device entries are organized by vendors and device IDs. Every line of text
in these sections must be one of the following 4 formats:
- Vendor definition. It is defined as shown below:
AudioVendor = 0x1102 "Audio card vendor name" // For audio vendor
DisplayVendor = 0x10b4 "Display card vendor name" // For display vendor
|
The definition begins with the AudioVendor or DisplayVendor keyword.
This is followed by '=' then by the vendor ID. Finally, a string follows
the vendor ID to provide the vendor's friendly name.
-
Device definition. A single device is defined like the following:
The definition starts with a device ID that uniquely defines the device for
the current vendor. It is followed by an equal sign, and a string that
contains the friendly name of the device. The word "unknown" can be
used in place of a device ID to represent all other devices not found in the
database from the current vendor.
- Custom property or reference to a named property set, as demonstrated below:
PropertyName1 // Name
PropertyName2 = Value // Name/Value
PropertySet = "MyPropSet" // Apply all properties included in MyPropSet
|
The first form above is useful for enabling or disabling a particular feature
that the application uses. For instance, a property could be used to
disable specular lighting. The second form is for when a parameter needs
to be set to a different value for some devices. An example would be
a property definition DepthBias=0.3 that forces the renderer to use a specific
value, 0.3 in this case, for the D3DRS_DEPTHBIAS render state. The third form
is a reference to the named property set "MyPropSet". Its effect is to apply
all properties in the property set "MyPropSet" as if they were listed here
instead of the named property set reference.
- Conditional property. Conditional properties are custom properties and named
property set references that are embedded in an if block, and are only applied
if a specified condition is true. The format is shown below:
if <identifier> operator <value>
PropertyName3 = Value
PropertySet = "MyPropSet"
endif
|
Valid identifiers and their value formats are:
Identifier | Value Format | Description |
ram | Number in decimal or hexadecimal (begins with 0x) | Amount of system memory. |
videoram | Number in decimal or hexadecimal (begins with 0x) | Amount of video memory. |
SubSysID | Number in decimal or hexadecimal (begins with 0x) | The subsystem ID of the hardware device. |
Revision | Number in decimal or hexadecimal (begins with 0x) | The revision number of the hardware device. |
driver | Full version format, defined as #.#.#.#, where # denotes a number in decimal or hexadecimal | The device's driver version. |
guid | A GUID in the format of ########-####-####-####-############ where each # denotes a hexadecimal digit | The device's GUID. |
OS | One of the following: Win95, Win98, Win98SE, WinME, Win2k, WinXP | The computer's operating system. |
Any DWORD members of the D3DCAPS9 struct | Number in decimal or hexadecimal (begins with 0x) | Caps,
Caps2, Caps3, PresentationIntervals, CursorCaps, DevCaps, PrimitiveMiscCaps,
RasterCaps, ZCmpCaps, SrcBlendCaps, DestBlendCaps, AlphaCmpCaps, ShadeCaps,
TextureCaps, TextureFilterCaps, CubeTextureFilterCaps, VolumeTextureFilterCaps,
TextureAddressCaps, VolumeTextureAddressCaps, LineCaps, MaxTextureWidth,
MaxTextureHeight, MaxVolumeExtent, MaxTextureRepeat, MaxTextureAspectRatio,
MaxAnisotropy, StencilCaps, FVFCaps, TextureOpCaps, MaxTextureBlendStages,
MaxSimultaneousTextures, VertexProcessingCaps, MaxActiveLights, MaxUserClipPlanes,
MaxVertexBlendMatrices, MaxVertexBlendMatrixIndex, MaxPrimitiveCount, MaxVertexIndex,
MaxStreams, MaxStreamStride, VertexShaderVersion, MaxVertexShaderConst, PixelShaderVersion
|
The operator can be one of the following:
= or == | True if the two operands are equal in value. |
<> or != | True if the two operands are not equal in value. |
>= | True if the left operand is greater than or equal to the right one in value. |
<= | True if the left operand is less than or equal to the right one in value. |
> | True if the left operand is greater than the right one in value. |
< | True if the left operand is less than the right one in value. |
& | Performs a bit-wise AND operation with the two operands and returns the result. |
Note that identifier and value should not be enclosed in any quotes.
In addition, if blocks can be nested.
Parsing the Device Database
This section details how the device database is parsed by the sample's parser.
The parsing for audio device properties and display device properties are handled
similarly but separately. Here, the parsing of display device properties will
be described, although the process also applies to audio device properties.
To match for a specific hardware device, the parser first searches for
a "DisplayVendor = 0xXXXX" line with a matching vendor ID. Once a matching
vendor definition is found, the parser skips vendor definitions after the
matching definition. This is done because sometimes a vendor has several
vendor IDs, and they are defined with several vendor definition lines.
Next, the parser looks for any and all custom properties
after the vendor definition but before a device definition. The properties
found here will be applied to all devices from that particular vendor.
Therefore, properties that apply to all devices from a particular vendor should
be placed immediately after the vendor definition when constructing the
database.
After parsing the vendor-wide properties, the parser continues to search for
a device definition line. If the parser finds a match before it encounters
a "break" line, it will parse all custom and conditional properties found until
it encounters a "break" line. The parser only stops parsing when it encounters a "break"
line. It does not stop when it finds another device definition. This behavior
is handy as it becomes easy to add properties that apply to multiple devices.
The following is an example database that defines properties for devices from a
particular vendor:
DisplayVendor = 0x1101 "Display Vendor Name"
DisplayVendor = 0x1102 "Display Vendor Name"
DisableSpecular // Disable specular lighting
0x1001 = "Card Model 1"
ForceShader=14 // Restrict shader to 1.4 or lower
0x1002 = "Card Model 2"
MaximumResolution=1024 // Max resolution 1024x768
break
0x1003 = "Card Model 3"
Unknown = "Unknown"
UseAnisotropicFilter // Use anisotropic filtering
break
|
In the above example, three devices from a display vendor are defined. The
vendor has two known vendor IDs, 0x1101 and 0x1102. DisableSpecular appears
before any device definition for this vendor. Therefore, it applies to all
devices from this vendor. For Card Model 1, two properties, ForceShader=14
and MaximumResolution=1024, will be applied in addition to the vendor-wide
property, DisableSpecular, as these two properties are parsed before a
"break" line is encountered. For Card Model 2, only MaximumResolution=1024 is
encountered before "break", so that is the only device-specific property
applicable to the device. For Card Model 3 and all other cards from this
vendor not listed in the database, only UseAnisotropicFilter will be applied,
plus the vendor-wide DisableSpecular property.
Specifying Default for All Devices
The database can specify a default set of properties to apply to all devices
from all vendors, whether or not they are included in the database. To
do this, the database can include two ApplyToAll property sets. One appears
before the hardware vendor definitions, and the other appears after. An
ApplyToAll property set has the syntax like the following:
ApplyToAll
Property1
Property2
break
|
ApplyToAll property sets, like any other property sets, should end with a "break" line.
The first ApplyToAll block is parsed before the hardware vendor section is, and
the second ApplyToAll block is parsed after parsing the hardware vendor
section. Therefore, the first block defines default properties that
can be overridden by vendor- or device-specific properties. The second
block defines default properties that cannot be overridden.
Custom Properties Implemented by the Sample
It is important to note that custom properties are completely designed
and supported by the application. The application has the best knowledge
of what it is attempting to do, and would know the best what to do with
a particular piece of hardware, whether to disable a feature or deploy
an alternative method. The sample implements a list of custom properties
that are more common to applications to show how they can typically used
and how application behavior is affected by the existence of custom properties.
These properties can and will most likely be modified when the configuration
system is used with a different application. The sample also lists a number
of additional properties that are not implemented in the sample, but may be
valuable to other applications because they have been needed by shipped
games. They serve the purpose of giving an idea of what type of problems
hardware and drivers can have.
The custom properties which are implemented by the sample are detailed below.
Property |
Value |
Description |
MaximumResolution |
640, 800, 1024, etc. |
This limits the maximum resolution used by the sample by specifying the
width of the resolution. For instance, to limit resolution to 800x600, use
MaximumResolution=800.
|
UseFixedFunction |
None |
This property tells the sample renderer to use fixed-function path.
When this property is not present, the renderer uses programmable shaders. In
the sample, fixed-function uses per-vertex lighting while pixel shader 2.0
or higher uses per-pixel lighting. The visual difference is dramatic. |
ForceShader |
0, 11, 12, 13, 14, 20, A2, B2, 30 |
This tells the renderer to use a particular pixel shader version,
even if the graphic device supports a newer version. For instance,
to force the renderer to use ps_1_4 on a 2.0 hardware, use
ForceShader=14. In the sample, pixel shader 2.0 or higher uses per-pixel
lighting. The visual difference is dramatic. |
UseAnisotropicFilter |
None |
This property tells the renderer to use anisotropic filtering for
textures. |
DisableSpecular |
None |
This tells the renderer to disable specular lighting. |
PrototypeCard |
None |
This property is used to warn the user about the presence of known
prototype cards. When this is present, the sample brings up a message
box to inform the user. |
UnsupportedCard |
None |
This property is used to disallow running the sample with cards
below specification. When this is present, the sample brings up a message
box to inform the user, then terminates. |
OldDriver |
None |
This property is used to warn the application user that the display driver
is older than the version tested by the developer. The sample brings up
a message box to inform the user. |
InvalidDriver |
None |
This property is used to disallow running the sample with display drivers
that are known to cause severe problems with the application. When this is present,
the sample brings up a message box to inform the user, then terminates. |
OldSoundDriver |
None |
This property is used to warn the application user that the sound driver
is older than the version tested by the developer. The sample brings up
a message box to inform the user. |
InvalidSoundDriver |
None |
This property is used to disallow running the sample with sound drivers
that are known to cause severe problems with the application. When this is present,
the sample brings up a message box to inform the user, then terminates. |
UMA |
None |
This property is used to alert the application that the display card on
the system is a UMA (Unified Memory Architecture) card. A UMA display card
uses part of the system memory as its video memory. When this property is
present, the amount of available video memory on the system is calculated
from the system memory size. |
The following properties are included in and parsed from the database,
but not implemented by the sample.
Property |
Value |
Description |
LinearTextureAddressing |
None |
When this is defined, non-square textures use unnormalized tex
coordinates instead of the normalized 0 to 1 range.
|
DoNotUseMinMaxBlendOp |
None |
When this is defined, the display device does not handle min
or max blending mode properly, so these should be avoided.
|
DisableDriverManagement |
None |
With this property, the Direct3D device should be created with
the D3DCREATE_DISABLE_DRIVER_MANAGEMENT flag so that resource
management is done by Direct3D, not the driver.
|
DisableAlphaRenderTargets |
None |
When this is defined, the application should not allow render
targets with an alpha channel.
|
DisableRenderTargets |
None |
All render targets should be disabled.
|
EnableStopStart |
None |
This property tells the application that the sound card supports
calling a DirectSound buffer's Start() and Stop() methods at a high
frequency.
|
Loading and Accessing the Database
When an application needs to access the content of the database, it
calls the static method IConfigDatabase::Create(), which initializes
and returns an IConfigDatabase object. This object represents the
database itself and is used to for the access. The application
should then call the IConfigDatabase::Load() method, passing in the
configuration database file path, a SOUND_DEVICE structure with
information about the sound device on the system, a D3DADAPTER_IDENTIFIER9
structure with similar information about the display device on the
system, the display cap bits as returned by Direct3D, the amount of
system and video memory, and the processor speed. Load() parses
the configuration database file and saves the requirements and
relevant custom properties based on the detected display and sound devices.
Internally, IConfigDatabase saves this information as a
PropertyName : Value string pair. Once the operation is complete,
the application can query for available requirements or custom properties and
obtain the string pairs.
Because the properties control how the application renders, the application
will very likely need to check the value of properties in a rendering
loop. As such, checking property values should take as little time as
possible to minimize possible performance impact. Because comparing
strings is not efficient as comparing two numbers, it is generally
not a good idea for the rendering loop to query properties directly
from IConfigDatabase. The CConfigManager helper class acts as a code layer
on top of IConfigDatabase. This application-customized class provides
detection for hardware on the system. It also queries its IConfigDatabase
for properties, then stores the property values in its member variables.
During rendering, the application can check the value of the member
variables to determine the appropriate behavior instead of querying
string pairs from IConfigDatabase.
Running in Safe Mode
The sample also demonstrates a simple implementation of safe mode. Safe
mode is a flag typically to signal the application to do everything in
the lowest possible settings, such as running at the lowest resolution
allowed, disabling all features that are not supported by every card
in existence, etc. To implement safe mode, the sample needs a way to tell
when application begins and ends expectedly, and saves the
status using a method that persists between two application runs, such as a file.
Therefore, the sample creates a zero-length file named Launch.sta at startup,
and deletes this file before it exits.
The location that the sample writes this file to is the subdirectory
<Drive>:\Document and Settings\<username>\Local Settings\Application Data\ConfigSystem,
where <username> is the logon name of the current user.
The folder "<Drive>\Document and Settings\<username>\Local Settings\Application Data" is
used by applications that are local to the system to store information for the current user.
It can be obtained by calling SHGetFolderPath() with CSIDL_LOCAL_APPDATA. The sample
uses this path instead of the application executable directory because it is the
best practice to write user-specific data to a location that is specific to the user.
Not every user will have access to the executable directory all the time. Therefore,
writing user-specific data to the executable directory should be avoided.
When the sample is launched and before it writes Launch.sta, it checks whether or not
Launch.sta already exists. If it does, then the application was in progress, and it
implies that the previous launch of the application encountered a problem and could not
exit normally. This is a good indication to turn on safe mode so that the
application can minimize the chance that the same problem occurs again.
In Summary
A configuration system using a database allows applications to precisely
define the behavior to employ for a specific hardware device. It is
worth noting that the properties in the database are defined by the
client application, and the application is free to extend the database
by adding more properties as it requires, or adding more hardware
vendors or devices as newer hardware becomes available. For applications
that get patched or updated, the most up-to-date configuration
database file can accompany the patch to ensure that customers with
newer hardware can still benefit from the system.
Disclaimer: To the best of our knowledge, the product names and capabilities
are accurate. If you are aware of any inaccuracies in this data, please inform us
at directx@microsoft.com.
Copyright (c) Microsoft Corporation. All rights reserved.
|