DirectXGraphics:
Texture Pooling
Author:
Jack Hoxley
Written: 29th April 2001
Contact: [Email]
Download: Graph_13.Zip
(29 kb)
Contents
of this lesson
1. Introduction
2. The requirements of a texture pooling
class
3. Making the class
4. Using the class
1.
Introduction
Welcome back for yet another
DirectXGraphics tutorial, the pace has
been quiet heavy recently - but this one
is going to be very simple (phew!). The
actual content of this tutorial is more
technique than feature - a way of
optimising your usage of textures in
Direct3D8. Currently all of the samples
in this series have never really taxed
the system resources, and at most
included 4-5 textures and 2-3 models,
but any commercial game or any
reasonably sized game will be using 50x
this. So how do you go about managing it
all? 50 textures is going to require a
moderate amount of memory (depending on
size), so you don't want any duplicates,
same goes for models/objects...
One solution to this is to create a
resource manager, there is a texture
manager built into the D3D subsystem,
but this isn't adequate for our
requirements - its useful, but we
require something a little cleverer.
With a resource manager your program can
tell it that it wants another texture to
be created (and other parameters), and
it will - but only if it decides that
you actually need it (for example, the
texture may already be loaded). You can
make this resource manager as simple or
as complex as you deem fit, BUT you want
it to remain a very thin interface, no
big chunky, slow and complicated code
here - we asked for a resource, we want
it now; as resources are so central to
your application a slow resource manager
will have a very big effect on the
overall speed of the application. Onto
the specification:
2.
The requirements
The
resource manager for this sample is
going to be kept fairly simple, but you
should be able to add and remove
features as they suit your program/games
environments. The key points are:
1.
To store the resources efficiently
2. To allow fast access to the resources
3. To stop multiple occurrences of a
resource
4. To perform memory management, freeing
unused/unwanted resources
5. To be a very thin interface
A
fairly obvious way for creating the
interface is to use a class module, some
people don't like them - and they are
provably slower in some cases, but the
added functionality in some cases is
worth it. Lets look at the 5 key
requirements in a little more detail:
1.
To store the resources efficiently
You could set it up so that there are
only 50 slots for textures/meshes, but I
don't like this method; instead we're
going to use an open ended array,
resizing it as we add new textures, and
to make sure no extra space is wasted
we'll go back and re-use any
deleted/empty slots when necessary.
2.
To allow fast access to the resources
This one is fairly simple, for every
resource created we return an index
value for the resource. The host
application need only store this number,
and when we want to use a resource we
pass the index back to the manager and
it'll return our resource for us. Easy.
3.
To stop multiple occurrences of a
resource
The easiest way to stop this is to build
a mini-database of some basic
information about the texture -size,
format, filename (complete) and then to
scan existing textures for identical
details - in such a case the texture
creation code should return the index
for the existing copy. It may well be
wise to allow an override switch for
this function - just in case the host
knows that there is going to be 2 or
more identical textures but intended it
that way (one is a backup for example).
4.
To perform memory management
It seems obvious to allow for a manual
delete function for a texture, and to
have an automatic cleanup function for
when the manager is destroyed; but an
on-the-fly automatic memory manager is a
little bit more complicated. It's
function would be to remove textures
that are no longer being used - the
difficult question being, how do you
know it's not being used? Two main
methods revolve around the least used
algorithm - either storing the last
access time for a texture (if its not
used for 30s remove it), or storing a
counter for the number of times it's
used per-frame, if it's used 0 times for
at least 30 frames remove it. Either of
these may work, or they may not work -
it's entirely up to you deciding what
works best with your game. This sample
will use the last access time method.
5.
Thin interface
This one comes simply with not having
much complicated code involved; whilst
this shouldn't affect functionality it's
a warning not too get too clever.
3.
Making the Class
The
first part to making the class is to
work out the declarations section, and
setup the basic public and private
structures. My sample looks like this:
'#############################################
'
' NAME: CTexMan
' AUTHOR: Jack Hoxley
' WRITTEN: April 29th 2001
'
' DESCRIPTION: Texture manager for Direct3D8
' CONTACT: www.vbexplorer.com/directx4vb/
' jollyjeffers@Greenonions.netscapeonline.co.uk
'
' DEPENDENCIES: DirectX8.0 runtime libraries
'
'#############################################
'## PUBLIC SETTINGS ##
Public bEnableAutoTextureManager As Boolean 'do they want us to handle texture culling?
Public lTextureIdleTime As Long 'how long before we remove a texture
'## PRIVATE SETTINGS ##
'User Defined Types
Private Type TexDBEntry
Filename As String
TexSizeW As Long
TexSizeH As Long
Fmt As CONST_D3DFORMAT
LastAccess As Long
End Type
'Arrays
Private TexDB() As TexDBEntry
Private TexList() As Direct3DTexture8
'Variables
Private lTexDBSize As Long 'how many entries are in the current array
Private lTexListSize As Long 'how many entries are in the current array
'API declarations
Private Declare Function GetTickCount Lib "kernel32" () As Long 'for timing
'Object References
Private D3DX As New D3DX8
|
|
|
|
not too
complicated really, in fact - it's
pretty simple! The only important parts
are those that come after
"Arrays" and
"Variables" - those are our
two most important sections, the rest of
the class module revolves around their
manipulation and usage. The next part to
look at is the Class_Initialize()
function - some simple configuration
must be done here - to stop the first
round of texture creations from failing:
Private Sub Class_Initialize()
lTexListSize = 1
lTexDBSize = 1
ReDim TexList(0 To lTexListSize) As Direct3DTexture8
ReDim TexDB(0 To lTexDBSize) As TexDBEntry
End Sub
|
|
|
|
whilst
this piece of code effectively say that
there is already 2 textures (0 and 1)
loaded into the program we'll see that
the actual texture creation code will
detect them as blank/unused entries and
will reuse them accordingly.... The next
important part to look at is the actual
CreateTexture() function - which is
effectively the only meat in this class
module:
Public Function CreateTexture(D3DDevice As Direct3DDevice8, Filename As String, TexSizeW As Long, _
TexSizeH As Long, Frmt As CONST_D3DFORMAT, ColorKey As Long, bOverride As Boolean) As Integer
On Error GoTo BailOut:
'//0. Any variables
Dim I As Long
'//1. Perform scan for existing entries
If override = False Then 'only scan if we aren't overriding...
For I = 0 To lTexListSize
If Not (TexList(I) Is Nothing) Then
'theres a texture loaded here at the moment...
If (TexDB(I).Filename = Filename) And (TexDB(I).TexSizeW = TexSizeW) And (TexDB(I).TexSizeH = TexSizeH) _
And (TexDB(I).Fmt = Frmt) Then
'perfect match!
CreateTexture = I
Exit Function
ElseIf (TexDB(I).Filename = Filename) Then
'reasonable match, most likely to be the same...
CreateTexture = I
'dont exit the function yet...
End If
End If
Next I
End If
If CreateTexture > 0 Then 'we found a partial match.
Exit Function 'return a pointer to the texture we found...
End If
'//2. find an empty slot
If CreateTexture = 0 Then 'only find an empty slot if we haven't found a match already....
For I = 0 To lTexListSize
If TexList(I) Is Nothing Then
'we found an unused slot
CreateTexture = I
GoTo JumpLoop:
End If
Next I
End If
If CreateTexture = 0 Then 'there are no free slots...
'we must create a slot...
lTexListSize = lTexListSize + 1
lTexDBSize = lTexDBSize + 1
ReDim Preserve TexList(0 To lTexListSize) As Direct3DTexture8
ReDim Preserve TexDB(0 To lTexDBSize) As TexDBEntry
CreateTexture = lTexListSize
End If
JumpLoop:
'//3. Load in the texture
Set TexList(CreateTexture) = D3DX.CreateTextureFromFileEx(D3DDevice, Filename, TexSizeW, TexSizeH, 1, 0, Frmt, _
D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, ColorKey, ByVal 0, ByVal 0)
TexDB(CreateTexture).Filename = Filename
TexDB(CreateTexture).Fmt = Frmt
TexDB(CreateTexture).TexSizeW = TexSizeW
TexDB(CreateTexture).TexSizeH = TexSizeH
TexDB(CreateTexture).LastAccess = GetTickCount() 'so that it's not culled away too quickly...
Exit Function
BailOut:
CreateTexture = -1 'indicate an error by returning -1
End Function
|
|
|
|
The basic
execution flow for this function goes
like this:
(1) Search For Existing Texture, if
found, exit function and return it's
index
(2) Find an empty slot to load a new
texture into, if none found, create a
new slot to load a texture into
(3) Load the texture into the
existing/new slot
The
rest of the functions are pretty
straight forward, and you can find them
in the downloadable source code example;
saves me filling up 2-3 pages of code
here...
4.
Using the Class
Okay,
so we now have a fully functioning class
- but you dont know how to use it yet.
It may seem obvious, but a little
demonstration may well be required. The
Class that I wrote for the sample is
actually embedded in a binary-compatible
ActiveX DLL - partly for reusability,
and partly for speed. I know that the
DLL works, if it's contained in it's own
little program (sort of..) then as long
as it continues to work then you dont
really need to bother yourself with WHY
it works, or how it does what it does -
only the HOW of using it. This is one of
the basic principles of OOP (Object
Orientated Programming), something
that's not always used in game
development - but I thought, for a
change, I may as well do so here.
To use
the DLL, all you need to do is register
it with your system, and then include it
in your project using the
Project>References menu command. To
register the DLL with your system, open
the Run dialog from the start menu and
type "Regsvr32 TexMan.DLL" -
after copying it to the
C:\windows\system folder (or
equivalent), if it's not in that place
(in your EXE's folder) then prefix
TexMan.DLL with the path to the DLL. To
add it to your project, click on the
project menu, then on the references
option - a new window appears with a
long list of libraries that you can link
to, find and select "Direct3D8
Texture Manager from www.vbexplorer.com/directx4vb/",
if you can find it, click on Browse and
navigate to the DLL file.
At the
beginning of this section I mentioned
the binary compatible ActiveX DLL;
binary compatibility is a great thing -
it allows you to change the DLL without
recompiling any programs that are
associated with it, to read up more on
this, see the VB
DLL tutorial.
Here's
the sample code from the host
application:
Dim DX As DirectX8
Dim D3D As Direct3D8
Dim D3DDevice As Direct3DDevice8
Dim TexManager As New TexMan.CTexMan 'Create our Texture Manager
Private Sub Form_Load()
Dim ResID(0 To 3) As Integer
Dim D3DWindow As D3DPRESENT_PARAMETERS
Dim DispMode As D3DDISPLAYMODE
TexManager.bEnableAutoTextureManager = True
TexManager.lTextureIdleTime = 30000 '30 seconds
Me.Show
Set DX = New DirectX8
Set D3D = DX.Direct3DCreate
D3D.GetAdapterDisplayMode 0, DispMode
D3DWindow.Windowed = 1 '//Tell it we're using Windowed Mode
D3DWindow.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC '//We'll refresh when the monitor does
D3DWindow.BackBufferFormat = DispMode.Format '//We'll use the format we just retrieved...
Set D3DDevice = D3D.CreateDevice(0, D3DDEVTYPE_REF, frmMain.hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, _
D3DWindow)
ResID(0) = TexManager.CreateTexture(D3DDevice, App.Path & "\tex0.bmp", 64, 64, D3DFMT_X1R5G5B5, 0, False)
ResID(1) = TexManager.CreateTexture(D3DDevice, App.Path & "\tex0.bmp", 64, 64, D3DFMT_X1R5G5B5, 0, False)
ResID(2) = TexManager.CreateTexture(D3DDevice, App.Path & "\tex1.bmp", 64, 64, D3DFMT_X1R5G5B5, 0, False)
ResID(3) = TexManager.CreateTexture(D3DDevice, App.Path & "\tex1.bmp", 64, 64, D3DFMT_X1R5G5B5, 0, False)
Debug.Print "ResID(0) = " & ResID(0)
Debug.Print "ResID(1) = " & ResID(1)
Debug.Print "ResID(2) = " & ResID(2)
Debug.Print "ResID(3) = " & ResID(3)
End Sub
|
|
|
|
it's
pretty useless as it stands, but it
illustrates the point of the texture
manager perfectly. Notice that to use
the textures we need only store 4
integers [ResID(0 to 3)] and at the end
it attempts to create 4 different
textures, from only 2 files. The final
output (in the immediate window) is:
ResID(0) = 0
ResID(1) = 0
ResID(2) = 1
ResID(3) = 1 |
|
|
|
Indicating
that there are only two textures loaded
(0 and 1) and that ID's 0,1 both
reference the same texture, as do 2 and
3... perfect!
Yet
another tutorial down - hopefully you've
not felt too strained by this
tutorial... and hopefully you can learn
from it and adapt it to your own needs;
to view the full source code, download
the Zip file from the top of the page or
the downloads
page. Next up: Lesson 14 : Advanced
Texturing Part 3 - Accessing Texture
memory for special effects
|