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