Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9

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:
'       DEPENDENCIES: DirectX8.0 runtime libraries

    Public bEnableAutoTextureManager As Boolean 'do they want us to handle texture culling?
    Public lTextureIdleTime As Long 'how long before we remove a texture


'User Defined Types
    Private Type TexDBEntry
        Filename As String
        TexSizeW As Long
        TexSizeH As Long
        Fmt As CONST_D3DFORMAT
        LastAccess As Long
    End Type
    Private TexDB() As TexDBEntry
    Private TexList() As Direct3DTexture8
    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

'//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
    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", 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

TexManager.bEnableAutoTextureManager = True
TexManager.lTextureIdleTime = 30000 '30 seconds


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...


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

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster