Attribute VB_Name = "DX8Window"
Option Explicit

'Declarations for Win32 API functions
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" _
        (lpMsg As MSG, ByVal hWnd As Long, ByVal wMsgFilterMin As Long, _
        ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long

Private Declare Function TranslateMessage Lib "user32" (lpMsg As MSG) As Long

Private Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" _
        (lpMsg As MSG) As Long

Private Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" _
        (Destination As Any, ByVal Length As Long)

Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) _
        As Long

'Pi (used in setting field of view)
Public Const PI = 3.14159

'Our screen/window sizes.  A better app would allow the user to choose the
'  sizes.  I'll do that in a later tutorial; for now this is good enough.
Public Const FM_WIDTH = 640
Public Const FM_HEIGHT = 480

'Our custom error codes
Public Const ERR_TUTORIAL_ABORT = vbObjectError + 1
Public Const ERR_UNSUPPORTED_CARD = vbObjectError + 2
Public Const ERR_USER_ABORT = vbObjectError + 3

'A global flag to determine if we're windowed (false) or full-screen (true)
Public gFullScreen As Boolean

'Our form object for the main window
Public gMainWindow As Form

'Our global flag to track whether we should quit or not.  When it becomes true,
'  we clean up and exit.
Public gAppDone As Boolean

'Our main Direct3D interface: it doesn't do much on its own, but all the more
'  commonly used interfaces are created by it.  It's the first D3D object you
'  create, and the last one you release.
Public gD3D As Direct3D8

'In previous lessons, we only needed the D3DX8 object in FatalError, but now
'  its use is becoming more widespread (we'll need it for manipulating our
'  texture), so we'll move it to a global variable now.
Public gD3DX As D3DX8

'The D3DDevice is your main rendering interface.  It represents the display and
'  all of its capabilities.  When you create, modify, or render any type of
'  resource, you will likely do it through this interface.
Public gD3DDevice As Direct3DDevice8

'A texture is an image that is to be 'applied' to a polygon/triangle.  By
'  'painting' our triangles with images we can achieve some amazing effects.
Public gTexture As Direct3DTexture8

'We add 2 floats to our vertex structure to store the texture coordinates.
'  tU & tV represent the location of the texture that should be mapped to this
'  vertex. tu can be thought of as the x-coordinate of the texture, and tv as
'  the y-coordinate.  So if a vertex was to be mapped to the top left of a
'  texture you would set tU & tV to (0,0).  Note-Texture coordinates range
'  from 0.0 (left, or top) to 1.0 (right, or bottom).
'NOTE: Since we want our texture to provide all of our colour information we
'  don't have a diffuse component in our vertex definition.
Private Type MyVertex
    X As Single         'The untransformed
    Y As Single         '  position for
    Z As Single         '  the vertex
    tU As Single        'The texture
    tV As Single        '  coordinates
End Type

'A handy little constant for our definition of the vertex.  When we use the
'  vertex data we have to tell D3D what data we're passing it.
'D3DFVF_XYZ specifies that the vertex will have coordinates given in model space.
'D3DFVF_TEX1 specifies that 1 set of texture coordinates will be provided as well.
Private Const D3D8T_CUSTOMVERTEX = D3DFVF_XYZ Or D3DFVF_TEX1

'This handy constant makes sure that we always reference the right number of
'  vertices, so that we always allocate a correctly sized vertex buffer.
Private Const NUM_VERTICES = 200

'Here we have the vertices for our pyramid and cube.
Private gVertices(NUM_VERTICES - 1) As MyVertex

'Vertex buffers are a method of storing vertices to be rendered in an
'  optimized manner.
Private gVB As Direct3DVertexBuffer8

'Main() is the first function called by Windows when our app is run.  It's the
'  entry point of our application.
Public Sub Main()
'Because each of the sub procedures called here has its own error handler,
'  this handler will only trap errors that we manually raise (e.g. in FatalError)
'  in order to return to Main and exit cleanly.
On Error GoTo End_Main

    'Prompt the user, Full Screen?  Windowed?  Cancel?
    gFullScreen = AskFullScreen()
    
    'Initialize our vertex coordinates
    InitVertices
    
    'Build our window.  Cover the screen if full-screen, otherwise make a
    '  standard window
    InitWindow
    
    'Build the D3D objects we'll require
    InitD3D
    
    'One-time preparation of objects and other stuff required for rendering
    InitScene
    
    'Loop until the user aborts (closes the window/hits a key/left-clicks)
    Do While Not gAppDone
        'Check for window messages
        MessagePump
        
        'Draw our incredibly cool graphics
        Render
    Loop
    
    'Clean up & exit
    Shutdown

End_Main:
    Exit Sub

End Sub

'Procedure: MessagePump
'Whazzit: Checks the message queue to see if any windows messages
'         (window is closing, window needs repainting, etc.) are waiting,
'         and if there are, the messages are dispatched to our
'         message handler.
Private Sub MessagePump()
On Error GoTo Err_MessagePump

    Dim oMsg As MSG
    
    If PeekMessage(oMsg, 0, 0, 0, PM_REMOVE) Then
        TranslateMessage oMsg
        DispatchMessage oMsg
    End If
    
    Exit Sub

Err_MessagePump:
    FatalError "Error in message handler"

End Sub

'Procedure: InitVertices
'Whazzit: Sets up the matrices that will be used to set up our vertex
'         buffers.
Private Sub InitVertices()

    'The last 2 properties in these array elements are the texture
    '  coordinates.  As mentioned before, their values range from 0.0 to
    '  1.0.  Our texture is a solid square and we want to map it on to the
    '  cube so that each face of the cube is covered by the texture.
    'Looking at the front face of our cube, we see that (-1,1) is the top
    '  left of the face; so we set its texture coordinates to (0,0), the
    '  top left of the texture.  Similarly, since vertex (1,-1) is the
    '  bottom right position, we set it to the bottom right (1,1) of the
    '  texture.
    'Front face:
    Dim tt As Integer
    For tt = 0 To 199
    With gVertices(tt)
        .X = Rnd * 8 - 4: .Y = Rnd * 8 - 4: .Z = Rnd * 8 - 4: .tU = Rnd * 1: .tV = Rnd * 1
    '        .x = tt / 10 - tt / 20: .y = Rnd * 4 - 2: .Z = Rnd * 4 - 2: .tU = Rnd * 1: .tV = Rnd * 1
    End With
    Next tt

End Sub

'Procedure: InitWindow
'Whazzit: Creates our main form with the appropriate settings (based on the
'         gFullScreen flag).
Private Sub InitWindow()
On Error GoTo Err_InitWindow

    'Initialize form based on the Window form template.  You could instantiate
    '  many forms using the same form definition.
    If gFullScreen Then
        Set gMainWindow = New FullScreen
    Else
        Set gMainWindow = Window
    End If
    
    With gMainWindow
        .MousePointer = vbArrow
        .BackColor = vbBlue
        .Caption = ""
        .ScaleMode = vbPixels
        .Top = 0
        .Left = 0
        
        If gFullScreen Then
            'If we're running full screen, we cover the desktop with our
            '  window.  This isn't necessary, but it provides a smoother
            '  transition for the user, especially when we're going to
            '  change screen modes.
          '''.Width = .ScaleX(GetSystemMetrics(SM_CXSCREEN), vbPixels, vbTwips)
           '''.Height = .ScaleY(GetSystemMetrics(SM_CYSCREEN), vbPixels, vbTwips)
            '.BorderStyle = vbBSNone
        Else
            'In windowed mode, we just make the window whatever size we need.
            '''.Width = .ScaleX(FM_WIDTH, vbPixels, vbTwips)
            '''.Height = .ScaleY(FM_HEIGHT, vbPixels, vbTwips)
            '.BorderStyle = vbFixedDouble
            '''.Caption = App.Title
        End If
        
        'Load our form and make it visible
        .Show vbModeless
        
        'Allow form to repaint before continuing
        DoEvents
    End With
    
    Exit Sub

Err_InitWindow:
    FatalError "Error in form generation"

End Sub

'Procedure: KillWindow
'Whazzit: Closes the window, cleans up any waiting messages, and then
'         releases the form reference.
Private Sub KillWindow()
On Error GoTo End_KillWindow

    Dim oMsg As MSG
    
    'Test if our window is valid
    If Not gMainWindow Is Nothing Then
        Unload gMainWindow
        
        'Clean up any pending messages
        Do While PeekMessage(oMsg, 0, 0, 0, PM_REMOVE)
            DispatchMessage oMsg
        Loop
    End If
    
    Set gMainWindow = Nothing
    
End_KillWindow:
    Exit Sub

End Sub

'Procedure: InitD3D
'Whazzit: Sets up Direct3D and creates the device.  The device is created differently
'         if we're full-screen as opposed to running in a desktop window.
Private Sub InitD3D()

    Dim oDX8 As New DirectX8
    Dim oD3Dpp As D3DPRESENT_PARAMETERS
    Dim oDisplayMode As D3DDISPLAYMODE
    
On Error GoTo Err_CreateD3D
    'Create Direct3D8, this is the first thing you have to do in any
    '  D3D8 program
    Set gD3D = oDX8.Direct3DCreate()
    Set oDX8 = Nothing
    Set gD3DX = New D3DX8
    
    'Get the current(desktop) display mode.  This is only needed if
    '  we're running in a window.
    gD3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT, oDisplayMode
    
    'Clear out our D3DPRESENT_PARAMETERS structure.  Even though we're
    '  going to set virtually all of its members, it's good practice to
    '  zero it out first.
    ZeroMemory oD3Dpp, Len(oD3Dpp)
    
'Turn off error handling in this function, so that any errors generated while
'  finding the display mode will be propogated back to Main() (from FatalError())
On Error GoTo 0
    With oD3Dpp
        'Whether we're full-screen or windowed these are the same.
        .SwapEffect = D3DSWAPEFFECT_DISCARD 'Throw away previous frames;
                                            '  we don't need them
        .hDeviceWindow = Window.Picture1.hWnd   'This is our main (and only) window
        .BackBufferCount = 1                'We only need a single back buffer
        
        'We need a Z-Buffer so everything will be drawn properly
        .EnableAutoDepthStencil = True
        .AutoDepthStencilFormat = D3DFMT_D16
        
        If gFullScreen Then
            'BackBufferWidth/Height have to be set for full-screen apps, these
            '  values are used (along with BackBufferFormat) to determine the
            '  display mode.  They aren't needed in windowed mode since the size
            '  of the window will be used.
            'BackBufferFormat is the pixel format we want.  In full-screen we
            '  need to find a pixel format we like, see find_16bit_mode()
            '  below for more details.
            .Windowed = False
            .BackBufferWidth = FM_WIDTH
            .BackBufferHeight = FM_HEIGHT
            .BackBufferFormat = Find16bitMode()
        Else
            .Windowed = True
            'In windowed mode we use the same format as the desktop (which we
            '  found by using GetAdapterDisplayMode() above).
            .BackBufferFormat = oDisplayMode.Format
        End If
    End With
    
On Error GoTo Err_CreateDevice
    'After filling in our D3DPRESENT_PARAMETERS structure, we're ready to create
    '  our device.
    'Most of the options in how the device is created are set in the
    '  D3DPRESENT_PARAMETERS structure.
    Set gD3DDevice = gD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _
             Window.Picture1.hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, oD3Dpp)
        'D3DADAPTER_DEFAULT: The default adapter; on a multimonitor system
        '  there can be more than one.
        'D3DDEVTYPE_HAL: Use hardware acceleration rather than the software
        '  renderer
        'gMainWindow.hWnd: Handle to our main window
        'D3DCREATE_SOFTWARE_VERTEXPROCESSING: Process vertices in software.
        '  This is slower than in hardware, but will work on all graphics cards.
        'oD3Dpp: Our D3DPRESENT_PARAMETERS structure, so it knows what we want
        '  to build
    
    Exit Sub

Err_CreateD3D:
    FatalError "Error getting Direct3D"
    Exit Sub

Err_CreateDevice:
    FatalError "Error creating device"
    Exit Sub

End Sub

'Procedure: KillD3D
'Whazzit: Frees all of our D3D resources in the opposite order from their creation.
Private Sub KillD3D()
    
    If Not gD3DDevice Is Nothing Then
        Set gD3DDevice = Nothing
    End If
    
    If Not gD3DX Is Nothing Then
        Set gD3DX = Nothing
    End If
    
    If Not gD3D Is Nothing Then
        Set gD3D = Nothing
    End If

End Sub

'Procedure: InitScene
'Whazzit: One-time preparation of objects required for rendering.  In this
'         tutorial we prepare 2 objects: a pyramid & a cube.
Private Sub InitScene()

    Dim iHR As Integer
    Dim oViewMatrix As D3DMATRIX
    Dim oMatProj As D3DMATRIX
    Dim oEyeVector As D3DVECTOR
    Dim oLookAtVector As D3DVECTOR
    Dim oUpVector As D3DVECTOR
    
On Error GoTo Err_LoadTexture
    'The following SetTextureStageState calls determine how textures will
    '  be applied to your objects in all of the following Draw operations.
    
    'D3DTSS_COLOROP specifies how the colour of each pixel will be determined
    '  In this case it's set to D3DTOP_SELECTARG1 which means we look to the
    '  setting of D3DTSS_COLORARG1 to determine the colour.
    gD3DDevice.SetTextureStageState 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1
    
    'D3DTSS_COLORARG1 is set to D3DTA_TEXTURE which means the colour is
    '  entirely taken from the texture and nothing else.  Alternately we could
    '  have set D3DTA_DIFFUSE and then the colour would have come from our
    '  vertex colour, completely ignoring the texture.
    gD3DDevice.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_TEXTURE
    
    'We don't want to use Alpha Blending (Transparency) so we make sure it's
    '  turned off for extra speed.
    gD3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, False
    
    'The MAGFILTER specifies how the texture should be filtered when it's
    '  drawn larger than its real size, MINFILTER is for when it's drawn
    '  smaller than its real size.
    '  The 2 most common choices are D3DTEXF_LINEAR & D3DTEXF_POINT.  Linear
    '  filtering is smoother but is also more expensive, point is fast but
    '  generally doesn't look as good.
    gD3DDevice.SetTextureStageState 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR
    gD3DDevice.SetTextureStageState 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR
    
    'Turn off D3D lighting.
    gD3DDevice.SetRenderState D3DRS_LIGHTING, 0
    
    'Turn on the zbuffer.  The ZBuffer makes sure that objects appear in the
    '  proper order.  Without it we'd have to make sure we draw our triangles
    '  in back-to-front order, with a ZBuffer we don't have to worry about it.
    gD3DDevice.SetRenderState D3DRS_ZENABLE, D3DZB_TRUE
    
    'backface culling
    gD3DDevice.SetRenderState D3DRS_CULLMODE, 1  '0
    
    'Given a file name for our bitmap, this method loads our texture to our
    '  gTexture object.
    Set gTexture = gD3DX.CreateTextureFromFile(gD3DDevice, App.Path & _
            "\DH.bmp")
    
On Error GoTo Err_InitVB
    'As mentioned above, a Vertex Buffer is an optimized storage medium for
    '  vertices.  Here we create a vertex buffer large enough to hold our
    '  pyramid's vertices.  We specify that it can only be written to and
    '  we allow Direct3D to determine where in memory it should be placed.
    Set gVB = gD3DDevice.CreateVertexBuffer(NUM_VERTICES * Len(gVertices(0)), _
            D3DUSAGE_WRITEONLY, D3D8T_CUSTOMVERTEX, D3DPOOL_MANAGED)
        'NUM_VERTICES * Len(gVertices(0)): Size of memory to be allocated
        '  Number of vertices * size of a vertex
        'D3DUSAGE_WRITEONLY: We never need to read from it so we specify
        '  write only; it's faster
        'D3D8T_CUSTOMVERTEX: Our custom vertex specifier (coordinates &
        '  a colour)
        'D3DPOOL_MANAGED: Tell DirectX to manage the memory of this resource
    
On Error GoTo Err_LockVB
    'Now we have our Vertex Buffer, but it's empty. To put our data into
    '  it we Lock the Vertex Buffer so Direct3D knows we're modifying it,
    '  copy our data in and Unlock it so Direct3D knows we're done.  The
    '  D3DVertexBuffer8SetData() function handles all 3 operations.
    iHR = D3DVertexBuffer8SetData(gVB, 0, NUM_VERTICES * Len(gVertices(0)), 0, _
            gVertices(0))
        'gVB: Our empty vertex buffer to be set
        '0: Offset, we want to start at the beginning
        'NUM_VERTICES * Len(gVertices(0)): Size of memory to be filled
        '0: Flags, nothing special
        'gVertices(0): Beginning of vertex array
    
    If iHR = D3DERR_INVALIDCALL Then
        Err.Raise D3DERR_INVALIDCALL, _
                "D3DVertexBuffer8SetData", _
                "Error locking/filling vertex buffer"
    End If
    
    'Here we build our View Matrix; think of it as our camera.
    
    'First we specify that our viewpoint is 8 units back on the Z-axis
    oEyeVector.X = 0!
    oEyeVector.Y = 0!
    oEyeVector.Z = -8!
    
    'We are looking towards the origin
    oLookAtVector.X = 0!
    oLookAtVector.Y = 0!
    oLookAtVector.Z = 0!
    
    'The "up" direction is the positive direction on the Y-axis
    oUpVector.X = 0!
    oUpVector.Y = 1!
    oUpVector.Z = 0!
    
    D3DXMatrixLookAtLH oViewMatrix, oEyeVector, oLookAtVector, oUpVector
    
    'Since our 'camera' will never move, we can set this once at the
    'beginning and never worry about it again
    gD3DDevice.SetTransform D3DTS_VIEW, oViewMatrix
    
    D3DXMatrixPerspectiveFovLH oMatProj, PI / 4, _
            CSng(FM_HEIGHT) / CSng(FM_WIDTH), 1!, 100!
        'oMatProj: Result Matrix
        'pi / 4: Field of View, in radians.  (pi/4) is typical
        'CSng(FM_HEIGHT) / CSng(FM_WIDTH): Aspect ratio is defined as
        '  width/height, but due to a bug in D3DX 8.1, we supply the
        '  inverse: height/width
        '1!: Near view plane
        '100!: Far view plane
    
    'Our Projection matrix won't change either, so we set it now
    '  and never touch it again.
    gD3DDevice.SetTransform D3DTS_PROJECTION, oMatProj

    Exit Sub

Err_LoadTexture:
    FatalError "Error loading texture"
    Exit Sub

Err_InitVB:
    FatalError "Error creating vertex buffer"
    Exit Sub

Err_LockVB:
    FatalError Err.Description
    Exit Sub

End Sub

'Procedure: KillScene
'Whazzit: Clean up any objects we required for rendering.
Private Sub KillScene()

    If Not gTexture Is Nothing Then
        Set gTexture = Nothing
    End If
    
    If Not gVB Is Nothing Then
        Set gVB = Nothing
    End If

End Sub

'Function: Find16bitMode
'Whazzit: Tests a couple of 16-bit modes to see if they are supported.  Virtually
'         every graphics card in existance will support one of these 2 formats.
Private Function Find16bitMode() As CONST_D3DFORMAT
On Error GoTo Err_Find16bitMode

    Dim iHR As Long
    
    'First we test for R5G6B5.  All 16-bits are used in this format, giving us a
    '  full 64K worth worth of colours.
    iHR = gD3D.CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _
             D3DFMT_R5G6B5, D3DFMT_R5G6B5, False)
    
    If iHR = D3D_OK Then
        Debug.Print "D3DFMT_R5G6B5"
        Find16bitMode = D3DFMT_R5G6B5
        Exit Function
    End If
    
    'Next try X1R5G5B5. Since 1 bit is wasted it's technically a 15-bit mode and
    '  only provides 32K colours, though you'd be hard pressed to tell the
    '  difference between 15- & 16-bit modes.
    iHR = gD3D.CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _
            D3DFMT_X1R5G5B5, D3DFMT_X1R5G5B5, False)
    
    If iHR = D3D_OK Then
        Debug.Print "D3DFMT_X1R5G5B5"
        Find16bitMode = D3DFMT_X1R5G5B5
        Exit Function
    End If
    
    'This is a freaky card.  Complain and bail out.
    Find16bitMode = D3DFMT_UNKNOWN
    Err.Raise ERR_UNSUPPORTED_CARD, "Find16bitMode", "Couldn't find a decent mode"
    
    Exit Function
    
Err_Find16bitMode:
    FatalError "Error finding 16-bit mode"

End Function

'Function: AskFullScreen
'Whazzit: Ask the user if they would like to run in full-screen or windowed mode
'         or if they would like to Cancel (abort).
Private Function AskFullScreen() As Boolean

    Dim iFullResult As VbMsgBoxResult
    Dim bFullScreen As Boolean
    
'''    iFullResult = MsgBox("Would you like to run in fullscreen mode?", _
            vbYesNoCancel + vbQuestion)
    
    iFullResult = vbNo
    Select Case iFullResult
        Case vbCancel
            'User hit 'Cancel' button, so we raise an error to take us back
            '  to Main and quit
            Debug.Print "User Abort"
            MsgBox "User Abort", vbOKOnly
            Err.Raise ERR_USER_ABORT, "AskFullScreen", "User Abort"
        Case vbNo
            'User hit 'No' button, run in a window
            bFullScreen = False
        Case vbYes
            'User hit 'Yes' button, run full-screen
            bFullScreen = True
        Case Else
            'Error!  Couldn't open dialog box
            Debug.Print "Couldn't open MsgBox, dying"
            Err.Raise ERR_TUTORIAL_ABORT, "AskFullScreen", _
                    "Couldn't open MsgBox"
    End Select
    
    AskFullScreen = bFullScreen

End Function

'Procedure: Render
'Whazzit: Draws our pretty(?) spinning cube
Private Sub Render()
Window.Text1 = Window.Text1 + 1
DoEvents
On Error GoTo Err_Render

    Dim oWorldMatrix As D3DMATRIX
    Dim oRotXMatrix As D3DMATRIX    'Our rotation matrix for the x-axis
    Dim oRotYMatrix As D3DMATRIX    'Our rotation matrix for the y-axis
    Dim oTransMatrix As D3DMATRIX   'Our translation matrix
    Static dRotX As Single          'Tracks rotation about the x-axis
    Static dRotY As Single          'Tracks rotation about the y-axis
    
    dRotX = dRotX + 0.007!
    dRotY = dRotY + 0.002!
    
    With gD3DDevice
        'Clear the buffer to our new colour and clear the ZBuffer.
        .Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, _
                D3DColorXRGB(0, 0, 0), 1!, 0
        
        'Notify the device that we're ready to render
        .BeginScene
        
        'Vertex shaders are a complex topic, but you can do some
        '  amazing things with them.  For this example we're not
        '  creating one, so we tell Direct3D that we're just using a
        '  plain vertex format.
        .SetVertexShader D3D8T_CUSTOMVERTEX
        
        'D3D's rendering functions read from streams.  Here we tell
        '  D3D that the VB we created for our triangle is the stream
        '  it should read from.
        gD3DDevice.SetStreamSource 0, gVB, Len(gVertices(0))
        
        'Here we set our texture to be the current texture in the first
        '  texture 'stage.'  There can be up to 8 textures and they can be
        '  blended in a variety of ways.  For now we only need one.
        gD3DDevice.SetTexture 0, gTexture
        
        'Set up the rotation matrix for the cube
        D3DXMatrixRotationY oRotYMatrix, dRotY
        D3DXMatrixRotationX oRotXMatrix, dRotX
        
        'Combine the 2 matrices to get our final World Matrix
        D3DXMatrixMultiply oWorldMatrix, oRotXMatrix, oRotYMatrix
        
        'Set our World Matrix
        gD3DDevice.SetTransform D3DTS_WORLD, oWorldMatrix
        
        'Draw our cube
        gD3DDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, NUM_VERTICES / 3
        
        'Notify the device that we're finished rendering for this frame
        .EndScene
        
        'After drawing, empty the first texture stage.  When you SetTexture
        '  a texture, Direct3D adds to its internal reference count; this
        '  prevents a texture from being released when it's still being used.
        '  When you're done drawing it's a good idea to set all of your
        '  texture stages to Nothing so your textures can be released when
        '  they should be.
        gD3DDevice.SetTexture 0, Nothing
        
        'Show the results
        .Present ByVal 0, ByVal 0, 0, ByVal 0
    End With
    
    Exit Sub

Err_Render:
    FatalError "Error rendering scene"

End Sub

'Procedure: FatalError
'Whazzit: Alerts the user, closes down all resources, and raises an error
'  to take us back to Main and exit
Private Sub FatalError(ByVal ErrorMsg As String)

    Dim iErrorNum As Long
    Dim sStdErrorDesc As String
    Dim sD3DErrorDesc As String
    
    'If an exception was generated, append its associated error message
    '  to the ErrorMsg string
    If Err.Number <> 0 Then
        'Save the error info; when we set a new error handler below,
        '  the properties of the Err object will be cleared.
        iErrorNum = Err.Number
        sStdErrorDesc = Err.Description
        
On Error GoTo Err_FatalError
        'Get the error description if this is a Direct3D error
        If Not gD3DX Is Nothing Then
            sD3DErrorDesc = gD3DX.GetErrorString(iErrorNum)
            
            ErrorMsg = ErrorMsg & vbCrLf & vbCrLf & "(#" & iErrorNum & _
                    ": " & sD3DErrorDesc & ")"
        End If
    End If

End_FatalError:
    'Clean up and prepare to exit
    Shutdown
    
    'Write our error message out to the debugger (if it's active)
    Debug.Print ErrorMsg & vbCrLf
    MsgBox ErrorMsg, vbOKOnly
    
'Turn off error handling for this procedure so that the next error raised
'  will be propogated back to Main() so we can quit
On Error GoTo 0
    Err.Raise ERR_TUTORIAL_ABORT, "FatalError", ErrorMsg
    
    Exit Sub
    
Err_FatalError:
    'The error obviously wasn't a Direct3D one.  Use the value we saved
    '  from Err.Description
    ErrorMsg = ErrorMsg & vbCrLf & vbCrLf & "(#" & iErrorNum & ": " & _
            sStdErrorDesc & ")"
    Resume End_FatalError

End Sub

'Procedure: Shutdown
'Whazzit: Closes down all resources and quits
Public Sub Shutdown()

    'Clean up all of our scene objects/resources
    KillScene
    
    'Clean up all of our Direct3D objects
    KillD3D
    
    'Close down our window
    KillWindow

End Sub
