freesci-develop
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[freesci-develop] Last DirectX driver update and moved to Linux


From: Alex Angas
Subject: [freesci-develop] Last DirectX driver update and moved to Linux
Date: Sat, 29 Mar 2003 18:52:15 +1030
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.3) Gecko/20030312

I have now permanently changed operating systems from Win32 to SuSE Linux and hence can no longer work on the Win32 port. (I know I'm preaching to the converted, but the software Microsoft charges so much for isn't worth paying when the same and better can be offered by the Linux community.)

While I'm no longer Win32ing, I'm really looking forward to getting stuck into whatever else needs to be done.

Attached is the last update made to the DirectX driver. Couldn't work
out how to make the mouse pointer native under DirectX so think I left
it turned off since it jerked all over the place. The screen still
judders when you move another window over the top of the FreeSCI window
as well and no idea why.

I'm very reluctant to CVS this from Linux myself as: (a) it's not set up and (b) I'll probably mess up the line breaks. Would you mind Matt? Sorry...

Cheers, Alex.
/***************************************************************************
 dx_driver.cpp Copyright (C) 2002,2003 Alexander R Angas,
               Copyright (C) 1999 Dmitry Jemerov

 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.

 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

                None

 History:

   20020817 - DirectX 8.0a (Win95+) support.
   20030113 - Submitted to CVS.
   20030115 - Fixed: memory leak in dx_grab_pixmap()
                        - Fixed: vertical line drawing
   20030116 - Fixed: all known memory leaks
   20030122 - Added: scaling and fullscreen
            - Fixed: Rearrange of draw_filled_rect() and draw_line()
   20030127 - Added: legacy mouse support
            - Fixed: shortened lines
   20030313 - Maintainer change
                        
                        - TODO: fix vertical screen shake
                        - TODO: change mouse pointer to DirectX
            - TODO: allow setting of adapter
                -- Alex Angas

***************************************************************************/

#ifdef HAVE_DIRECTX

#ifndef __cplusplus
#error NOTE: This file MUST be compiled as C++. In Visual C++, use the /Tp 
command line option.
#endif

#include <windows.h>
#include <windowsx.h>
#include <d3d8.h>
#include <d3dx8math.h>
#include <dxerr8.h>

#if (DIRECT3D_VERSION < 0x0800)
#error ERROR: DirectX 8.0a or higher is required for this driver.
#endif

extern "C" {
#include <gfx_system.h>
#include <gfx_driver.h>
#include <gfx_tools.h>
#include <assert.h>
#include <uinput.h>
#include <ctype.h>
#include <console.h>
#include <sci_win32.h>
#include <sci_memory.h>
};


#define DODX(cmd, proc)        \
                hr = cmd;              \
                if ( hr != D3D_OK ) {  \
                        sciprintf("%s(): Failure in %i\n  %s\n  %s\n", #proc, 
__LINE__, #cmd, DXGetErrorString8(hr));  \
                        BREAKPOINT();      \
                }

#define SAFE_RELEASE(p)  \
        if (p)               \
                (p)->Release();

#define dx_state ((struct gfx_dx_struct_t *)(drv->state))

#define MAP_KEY(x,y) case x: add_key_event (drv, y); break


#define DX_CLASS_NAME "FreeSCI DirectX Graphics"
#define WINDOW_SUFFIX " Window"


struct PIXMAP_VERTEX
{
        D3DXVECTOR4 p;
        DWORD col;                      // Diffuse colour
        D3DXVECTOR2 t;          // 2D texture coordinates
};
#define D3DFVF_PIXMAP_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)


#define SCI_DX_HANDLE_NORMAL 0
#define SCI_DX_HANDLE_GRABBED 1

#define NUM_VISUAL_BUFFERS              3
#define NUM_PRIORITY_BUFFERS    2

#define PRIMARY_VIS     0
#define BACK_VIS        1
#define STATIC_VIS      2

#define BACK_PRI        0
#define STATIC_PRI      1

#define DXFLAGS_FULLSCREEN  1

struct gfx_dx_struct_t
{
        LPDIRECT3D8 g_pD3D;                             // D3D object
        D3DCAPS8 g_d3dcaps;                             // Capabilities of 
device
        D3DDISPLAYMODE g_d3ddm;                 // Width and height of screen
        D3DPRESENT_PARAMETERS g_d3dpp;  // Presentation parameters
        LPDIRECT3DDEVICE8 g_pd3dDevice; // Rendering device

        LPDIRECT3DVERTEXBUFFER8 g_pPVB; // Buffer to hold pixmap vertices
        //UINT pvNum = 4;                                               // 
Count of pixmap vertices
        PIXMAP_VERTEX pvData[4];                // Buffer of pixmap vertex 
structs

        LPDIRECT3DTEXTURE8 visuals[NUM_VISUAL_BUFFERS];
        LPDIRECT3DTEXTURE8 priority_visuals[NUM_PRIORITY_BUFFERS];
        gfx_pixmap_t *priority_maps[NUM_PRIORITY_BUFFERS];

        BOOL showWinCursor;
        BOOL showDXCursor;

        WNDCLASSEX wc;          // Window class
        HWND hWnd;                      // Window
        RECT clientCoords;      // Coordinates of the display window
        UINT xsize, ysize;
        UINT xfact, yfact;
        UINT bpp;

        // event queue
        int queue_size, queue_first, queue_last;
        sci_event_t *event_queue;
};

static int flags = 0;

static int ProcessMessages(struct _gfx_driver *drv);


/* Properly draws the scene. */
static gfx_return_value_t
Render2D(struct _gfx_driver *drv)
{
        HRESULT hr;

    // Render the pixmaps
        DODX((dx_state->g_pd3dDevice->SetTexture( 0, 
dx_state->visuals[PRIMARY_VIS] )), Render2D);
        DODX((dx_state->g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,  
 D3DTOP_MODULATE )), Render2D);
        DODX((dx_state->g_pd3dDevice->SetTextureStageState( 0, 
D3DTSS_COLORARG1, D3DTA_TEXTURE )), Render2D);
        DODX((dx_state->g_pd3dDevice->SetTextureStageState( 0, 
D3DTSS_COLORARG2, D3DTA_CURRENT )), Render2D);
        DODX((dx_state->g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,  
 D3DTOP_DISABLE )), Render2D);

        // Assume vertex buffer has already been set up
        DODX((dx_state->g_pd3dDevice->SetStreamSource( 0, dx_state->g_pPVB, 
sizeof(PIXMAP_VERTEX) )), Render2D);
        DODX((dx_state->g_pd3dDevice->SetVertexShader( D3DFVF_PIXMAP_VERTEX )), 
Render2D);
        DODX((dx_state->g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 
)), Render2D);

        // Check if device has been lost
        HRESULT lostDevice = dx_state->g_pd3dDevice->TestCooperativeLevel();
        if (lostDevice == D3DERR_DEVICELOST)
        {
                sciprintf("DirectX device has been lost and cannot be 
reset.\n");
        }
        else if (lostDevice == D3DERR_DEVICENOTRESET)
        {
                sciprintf("DirectX device has been lost and can be reset.\n");
        }

        return GFX_OK;
}

/* Abstractly draws the scene. */
static gfx_return_value_t
Render(struct _gfx_driver *drv)
{
        HRESULT hr;

        // Do nothing if no D3D device
        if( dx_state == NULL )
                return GFX_OK;
        if( dx_state->g_pd3dDevice == NULL )
                return GFX_OK;

        // Clear the back buffer
        DODX((dx_state->g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, 
D3DCOLOR_XRGB(0,0,0), 1.0f, 0 )), Render);

        // Begin the scene
        DODX((dx_state->g_pd3dDevice->BeginScene()), Render);

        // Render
        Render2D(drv);

        // End the scene
        DODX((dx_state->g_pd3dDevice->EndScene()), Render);

        // Present the backbuffer contents to the display
        DODX((dx_state->g_pd3dDevice->Present( NULL, NULL, NULL, NULL )), 
Render);

        return GFX_OK;
}


static gfx_return_value_t
InitD3D(struct _gfx_driver *drv)
{
        HRESULT hr;

        sciprintf("Setting up DirectX Graphics...\n");

        // Create Direct3D object.
        dx_state->g_pD3D = Direct3DCreate8( D3D_SDK_VERSION );
        if ( FAILED( dx_state->g_pD3D ) ) {
                sciprintf("InitD3D(): Direct3DCreate8 failed\n");
                return GFX_FATAL;
        }

        // Look for adapters.
        for ( UINT adapterLoop = 0; adapterLoop < 
dx_state->g_pD3D->GetAdapterCount(); adapterLoop++ )
        {
                D3DADAPTER_IDENTIFIER8 adapterId;
                DODX((dx_state->g_pD3D->GetAdapterIdentifier(adapterLoop, 
D3DENUM_NO_WHQL_LEVEL, &adapterId)), InitD3D);
                if ( FAILED( hr ) )
                        break;
                if (adapterId.Driver[0] == '\0')
                        break;
                sciprintf(" Adapter %u: %s\n", adapterLoop++, 
adapterId.Description);
        }

        // Get device caps.
        DODX((dx_state->g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, 
D3DDEVTYPE_HAL, &(dx_state->g_d3dcaps))), InitD3D);

        // Get current display mode and optimise for this screen.
        DODX((dx_state->g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, 
&dx_state->g_d3ddm )), InitD3D);
        sciprintf("   Display %lu x %lu\n", dx_state->g_d3ddm.Width, 
dx_state->g_d3ddm.Height);
        if (dx_state->xfact == 0)
                dx_state->xfact = (int)(dx_state->g_d3ddm.Width / 320);
        dx_state->yfact = dx_state->xfact;

        return GFX_OK;
}


static gfx_return_value_t
ConfigD3D(struct _gfx_driver *drv)
{
        HRESULT hr;

        sciprintf("Configuring DirectX Graphics...\n");

        // Set D3D behaviour.
        ZeroMemory( &dx_state->g_d3dpp, sizeof(D3DPRESENT_PARAMETERS) );
        dx_state->g_d3dpp.BackBufferWidth  = dx_state->xsize;
        dx_state->g_d3dpp.BackBufferHeight = dx_state->ysize;
        dx_state->g_d3dpp.BackBufferFormat = dx_state->g_d3ddm.Format;
        dx_state->g_d3dpp.BackBufferCount = 1;
        dx_state->g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        dx_state->g_d3dpp.hDeviceWindow = dx_state->hWnd;
        dx_state->g_d3dpp.Windowed = TRUE;

        if (flags & DXFLAGS_FULLSCREEN)
        {
                if (dx_state->g_d3dcaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED) {
                        dx_state->g_d3dpp.Windowed = FALSE;
                } else {
                        sciprintf("Sorry, DirectX will not render in full 
screen with your video card\n");
                }
        }

        // Create D3D device.
        DODX((dx_state->g_pD3D->CreateDevice(
                D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, dx_state->hWnd,
                D3DCREATE_HARDWARE_VERTEXPROCESSING,
                &dx_state->g_d3dpp, &dx_state->g_pd3dDevice)), ConfigD3D );

    // Set render states.
        DODX((dx_state->g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, 
D3DZB_FALSE )), ConfigD3D);
        DODX((dx_state->g_pd3dDevice->SetRenderState( D3DRS_SHADEMODE, 
D3DSHADE_FLAT )), ConfigD3D);
        DODX((dx_state->g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, 
D3DCULL_NONE )), ConfigD3D);
        DODX((dx_state->g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE )), 
ConfigD3D);
        DODX((dx_state->g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 
RGB(255,255,255) )), ConfigD3D);

        // Create textures.
        int i;
        for (i = 0; i < NUM_VISUAL_BUFFERS; i++) {
                DODX((dx_state->g_pd3dDevice->CreateTexture(dx_state->xsize, 
dx_state->ysize,
                        1, 0,
                        D3DFMT_A8R8G8B8,
                        D3DPOOL_MANAGED,
                        &dx_state->visuals[i])), ConfigD3D);
        }
        for (i = 0; i < NUM_PRIORITY_BUFFERS; i++) {
                DODX((dx_state->g_pd3dDevice->CreateTexture(dx_state->xsize, 
dx_state->ysize,
                        1, 0,
                        D3DFMT_P8,
                        D3DPOOL_MANAGED,
                        &dx_state->priority_visuals[i])), ConfigD3D);
        }

        // Set up pixmap vertex buffers.
        DODX((dx_state->g_pd3dDevice->CreateVertexBuffer( 4 * 
sizeof(PIXMAP_VERTEX),
                                                                                
                0, D3DFVF_PIXMAP_VERTEX,
                                                                                
                D3DPOOL_MANAGED,
                                                                                
                &dx_state->g_pPVB )), ConfigD3D);

        dx_state->pvData[0].p = D3DXVECTOR4(                  0.0f,             
      0.0f, 0.0f, 1.0f);
    dx_state->pvData[1].p = D3DXVECTOR4((float)dx_state->xsize,                 
  0.0f, 0.0f, 1.0f);
    dx_state->pvData[2].p = D3DXVECTOR4(                  0.0f, 
(float)dx_state->ysize, 0.0f, 1.0f);
    dx_state->pvData[3].p = D3DXVECTOR4((float)dx_state->xsize, 
(float)dx_state->ysize, 0.0f, 1.0f);
        dx_state->pvData[0].col = dx_state->pvData[1].col = 
dx_state->pvData[2].col = dx_state->pvData[3].col = 0xffffffff;
        dx_state->pvData[0].t = D3DXVECTOR2(0.0f, 0.0f);
        dx_state->pvData[1].t = D3DXVECTOR2(1.0f, 0.0f);
        dx_state->pvData[2].t = D3DXVECTOR2(0.0f, 1.0f);
        dx_state->pvData[3].t = D3DXVECTOR2(1.0f, 1.0f);

        VOID *ptr;
        DODX((dx_state->g_pPVB->Lock(0, 0, (BYTE**)&ptr, 0)), ConfigD3D);
        memcpy(ptr, dx_state->pvData, sizeof(dx_state->pvData));
        DODX((dx_state->g_pPVB->Unlock()), ConfigD3D);

        return GFX_OK;

}


LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
        return DefWindowProc( hWnd, msg, wParam, lParam );
}


/* Sets a driver-specific parameter. */
static int
dx_set_param(struct _gfx_driver *drv, char *attribute, char *value)
{
        if (!strncmp(attribute, "fullscreen", 11)) {
                if (string_truep(value))
                        flags |= DXFLAGS_FULLSCREEN;
                else
                        flags &= ~DXFLAGS_FULLSCREEN;

                return GFX_OK;
        }

        sciprintf("Attempt to set DirectX parameter \"%s\" to \"%s\" failed\n", 
attribute, value);
        return GFX_ERROR;
}


/* Attempts to initialize a specific graphics mode. */
static int
dx_init_specific(struct _gfx_driver *drv,
                                 int xfact, int yfact,  /* horizontal and 
vertical scaling */
                                 int bytespp)                   /* must be 
value 4 */
{
        int red_shift = 8, green_shift = 16, blue_shift = 24, alpha_shift = 32;
        int alpha_mask = 0x00000000, red_mask = 0x00ff0000, green_mask = 
0x0000ff00, blue_mask = 0x000000ff;
        gfx_return_value_t d3dret;

        drv->state = (struct gfx_dx_struct_t *) 
sci_malloc(sizeof(gfx_dx_struct_t));
        ZeroMemory(drv->state, sizeof(gfx_dx_struct_t));

        // Check for scaling
        if (xfact != 0)
                dx_state->xfact = xfact;
        if (yfact != 0)
                dx_state->yfact = yfact;

        // Set up Direct3D for this screen
        d3dret = InitD3D(drv);
        if (d3dret != GFX_OK)
                return d3dret;

        // Set factor according to command line here
        dx_state->xsize = dx_state->xfact * 320;
        dx_state->ysize = dx_state->yfact * 200;
        dx_state->bpp = bytespp;

        // Register the window class.
        ZeroMemory( &(dx_state->wc), sizeof(WNDCLASSEX) );
        dx_state->wc.cbSize = sizeof(WNDCLASSEX);
        dx_state->wc.style = CS_HREDRAW | CS_VREDRAW;
        dx_state->wc.lpfnWndProc = MsgProc;
        dx_state->wc.hInstance = GetModuleHandle(NULL);
        dx_state->wc.hCursor = NULL;
        dx_state->wc.lpszClassName = DX_CLASS_NAME;
        if ( RegisterClassEx( &dx_state->wc ) == 0 )
        {
                sciprintf("dx_init_specific(): RegisterClassEx failed (%u)\n", 
GetLastError());
                return GFX_FATAL;
        }

        // Create the application's window.
        dx_state->hWnd = CreateWindow(
                DX_CLASS_NAME, "FreeSCI",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT, dx_state->xsize, dx_state->ysize,
                GetDesktopWindow(), NULL, dx_state->wc.hInstance, NULL );

        if ( dx_state->hWnd == NULL )
        {
                sciprintf("dx_init_specific(): CreateWindow failed (%u)\n", 
GetLastError());
                return GFX_FATAL;
        }

        for (int i = 0; i < NUM_PRIORITY_BUFFERS; i++) {
                dx_state->priority_maps[i] = 
gfx_pixmap_alloc_index_data(gfx_new_pixmap(dx_state->xsize, dx_state->ysize, 
GFX_RESID_NONE, -i, -777));
                if (!dx_state->priority_maps[i]) {
                        GFXERROR("Out of memory: Could not allocate priority 
maps! (%dx%d)\n", dx_state->xsize, dx_state->ysize);
                        return GFX_FATAL;
                }
                dx_state->priority_maps[i]->flags |= 
GFX_PIXMAP_FLAG_SCALED_INDEX;
        }

        // Configure Direct3D stuff.
        d3dret = ConfigD3D(drv);
        if (d3dret != GFX_OK)
                return d3dret;

        // Show the window
        ShowWindow( dx_state->hWnd, SW_SHOWDEFAULT );
        UpdateWindow( dx_state->hWnd );

        // Set up the driver's state
        dx_state->queue_first = 0;
        dx_state->queue_last  = 0;
        dx_state->queue_size  = 256;
        dx_state->event_queue = (sci_event_t *) sci_malloc 
(dx_state->queue_size * sizeof (sci_event_t));

        // Set up graphics mode
        drv->mode = gfx_new_mode(dx_state->xfact, dx_state->yfact, 
dx_state->bpp,
                           red_mask, green_mask, blue_mask, alpha_mask,
                           red_shift, green_shift, blue_shift, alpha_shift,
                           (dx_state->bpp == 1) ? 256 : 0, 0);

        // Set mouse cursor position
        GetClientRect(dx_state->hWnd, &dx_state->clientCoords);
//      update_mousepos(drv);

        return GFX_OK;
}


/* Initialize 'most natural' graphics mode. */
static int
dx_init(struct _gfx_driver *drv)
{
        return dx_init_specific(drv, NULL, NULL, 4);
}


/* Uninitializes the current graphics mode. */
static void
dx_exit(struct _gfx_driver *drv)
{
        int i;

        if(drv->state == NULL)
                return;

        dx_state->g_pd3dDevice->ShowCursor(FALSE);
        ShowCursor(TRUE);
        for (i = 0; i < NUM_PRIORITY_BUFFERS; i++)
                SAFE_RELEASE( dx_state->priority_visuals[i] );
        for (i = 0; i < NUM_VISUAL_BUFFERS; i++)
                SAFE_RELEASE( dx_state->visuals[i] );
        SAFE_RELEASE( dx_state->g_pPVB );
    SAFE_RELEASE( dx_state->g_pd3dDevice );
    SAFE_RELEASE( dx_state->g_pD3D );

        if ( dx_state->event_queue )
                sci_free(dx_state->event_queue);
        dx_state->queue_size = 0;

        for (i = 0; i < NUM_PRIORITY_BUFFERS; i++)
                gfx_free_pixmap(drv, dx_state->priority_maps[i]);

    UnregisterClass( DX_CLASS_NAME WINDOW_SUFFIX, dx_state->wc.hInstance );
        DestroyWindow(dx_state->hWnd);

        sci_free(dx_state);
}


/*** Drawing operations. ***/

/* Draws a single filled and possibly shaded rectangle to the back buffer. */
static int
dx_draw_filled_rect(struct _gfx_driver *drv, rect_t box,
                           gfx_color_t color1, gfx_color_t color2,
                           gfx_rectangle_fill_t shade_mode)
{
        if (color1.mask & GFX_MASK_VISUAL)
        {
                HRESULT hr;
                D3DLOCKED_RECT lockedRect;

                // Calculate colour value for line pixel
                UINT lineColor = (color1.alpha << 24) | (color1.visual.r << 16) 
| (color1.visual.g << 8) | color1.visual.b;
                RECT r = { box.x, box.y, box.x + box.xl, box.y + box.yl };
                RECT lr = r;

                // Fix bounds
                if (lr.left == lr.right)
                        lr.right++;
                if (lr.top == lr.bottom)
                        lr.bottom++;
                if (r.right > (int)dx_state->xsize)
                        lr.right = r.right = dx_state->xsize;
                if (r.bottom > (int)dx_state->ysize)
                        lr.bottom = r.bottom = dx_state->ysize;

//sciprintf("%08X  %i,%i -> %i,%i\n", lineColor, r.left, r.top, r.right, 
r.bottom);

                DODX( (dx_state->visuals[BACK_VIS]->LockRect(0, &lockedRect, 
&lr, 0)), dx_draw_line );
                UINT *rectPtr = (UINT*)lockedRect.pBits;

                // Going along x axis
                for (int y_pixel = r.top; y_pixel < r.bottom; y_pixel++)
                {
                        UINT *startLine = rectPtr;
                        for (int x_pixel = r.left; x_pixel < r.right; x_pixel++)
                        {
                                *rectPtr = lineColor;
                                rectPtr++;
                        }
                        rectPtr = startLine;
                        rectPtr += dx_state->xsize;
                }

                DODX( (dx_state->visuals[BACK_VIS]->UnlockRect(0)), 
dx_draw_line );
        }

        if (color1.mask & GFX_MASK_PRIORITY)
        {
                byte *pri;
                pri = dx_state->priority_maps[BACK_PRI]->index_data + box.x + 
box.y*(dx_state->xsize);
                for(int i=0; i<box.yl; i++)
                {
                        memset(pri,color1.priority,box.xl);
                        pri += dx_state->xsize;
                }
        }

        return GFX_OK;
}


/* Draws a single line to the back buffer. */
static int
dx_draw_line(struct _gfx_driver *drv, rect_t line, gfx_color_t color,
                    gfx_line_mode_t line_mode, gfx_line_style_t line_style)
{
        if (color.mask & GFX_MASK_VISUAL) {

                // Calculate line thickness
                int xf = (line_mode == GFX_LINE_MODE_FINE) ? 1 : 
dx_state->xfact;
                int yf = (line_mode == GFX_LINE_MODE_FINE) ? 1 : 
dx_state->yfact;
                
                rect_t line_rect = { line.x, line.y, line.xl, line.yl };
                if (line_rect.xl == 0) {
                        // Line parallel to x axis
                        line_rect.xl += xf;
                        line_rect.yl += yf;
                }
                if (line_rect.yl == 0) {
                        // Line parallel to y axis
                        line_rect.yl += yf;
                        line_rect.xl += xf;
                }

                // Make sure priorities are not updated in dx_draw_filled_rect()
                gfx_color_t col = color;
                col.mask = GFX_MASK_VISUAL;

                dx_draw_filled_rect(drv, line_rect, col, col, GFX_SHADE_FLAT);
        }

        if (color.mask & GFX_MASK_PRIORITY) {
                unsigned int xc, yc;
                rect_t newline;

                newline.xl = line.xl;
                newline.yl = line.yl;

                for (xc = 0; xc < dx_state->xfact; xc++)
                        for (yc = 0; yc < dx_state->yfact; yc++) {
                                newline.x = line.x + xc;
                                newline.y = line.y + yc;
                                
gfx_draw_line_pixmap_i(dx_state->priority_maps[BACK_PRI], newline, 
color.priority);
                        }
        }

        return GFX_OK;
}


/*** Pixmap operations. ***/

static int
dx_register_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm)
{
        HRESULT hr;

        int i, xs;
        byte *s, *d;
        D3DLOCKED_RECT lockedRect;
        LPDIRECT3DTEXTURE8 newTex;
        DODX( (dx_state->g_pd3dDevice->CreateTexture(pxm->xl, pxm->yl, 1, 0, 
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &newTex )), dx_register_pixmap );

        // Do gfx crossblit
        DODX( (newTex->LockRect(0, &lockedRect, NULL, 0)), dx_register_pixmap );
        s = pxm->data;
        d = (byte *) lockedRect.pBits;
        xs = drv->mode->bytespp * pxm->xl;

        for(i = 0; i < pxm->yl; i++)
        {
                memcpy(d, s, xs);
                s += xs;
                d += lockedRect.Pitch;
        }
        DODX( (newTex->UnlockRect(0)), dx_register_pixmap );

        pxm->internal.info = newTex;
        pxm->internal.handle = SCI_DX_HANDLE_NORMAL;

        return GFX_OK;
}


static int
dx_unregister_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm)
{
        SAFE_RELEASE((LPDIRECT3DTEXTURE8) (pxm->internal.info));
        pxm->internal.info = NULL;

        return GFX_OK;
}


/* Draws part of a pixmap to the static or back buffer. */
static int
dx_draw_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm, int priority,
                      rect_t src, rect_t dest, gfx_buffer_t buffer)
{
        HRESULT hr;
        int bufnr = (buffer == GFX_BUFFER_STATIC) ? 2 : 1;
        int pribufnr = bufnr - 1;
        LPDIRECT3DTEXTURE8 srct, dstt;
        LPDIRECT3DSURFACE8 sbuf, dbuf;
        D3DLOCKED_RECT lockedRect;
        byte *pri_map = NULL;

        if (pxm->internal.handle == SCI_DX_HANDLE_GRABBED) {
                // copy from pxm->internal.info to visual[bufnr]
                RECT srcRect = RECT_T_TO_RECT(src);
                POINT dstPoint = { dest.x, dest.y };

                srct = (LPDIRECT3DTEXTURE8) (pxm->internal.info);
                dstt = dx_state->visuals[bufnr];

                DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
                DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
                DODX( (dx_state->g_pd3dDevice->CopyRects(sbuf, &srcRect, 1, 
dbuf, &dstPoint)), dx_draw_pixmap );

                SAFE_RELEASE(sbuf);
                SAFE_RELEASE(dbuf);

                return GFX_OK;
        }


        // Create texture to temporarily hold visuals[bufnr]
        LPDIRECT3DTEXTURE8 temp;
        DODX( (dx_state->g_pd3dDevice->CreateTexture(pxm->xl, pxm->yl, 1, 0, 
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &temp)), dx_draw_pixmap );
        RECT srcRect = RECT_T_TO_RECT(dest);
        RECT destRect = { 0, 0, dest.xl, dest.yl };
        POINT dstPoint = { destRect.left, destRect.top };

        // Copy from visuals[bufnr] to temp
        srct = dx_state->visuals[bufnr];
        dstt = temp;
        DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
        DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
        DODX( (dx_state->g_pd3dDevice->CopyRects(sbuf, &srcRect, 1, dbuf, 
&dstPoint)), dx_draw_pixmap );

        // Copy from given pixmap to temp
        DODX( (dbuf->LockRect(&lockedRect, &destRect, 0)), dx_draw_pixmap );
        gfx_crossblit_pixmap(drv->mode, pxm, priority, src, dest,
                       (byte *) lockedRect.pBits, lockedRect.Pitch,
                       dx_state->priority_maps[pribufnr]->index_data,
                       dx_state->priority_maps[pribufnr]->index_xl, 1,
                       GFX_CROSSBLIT_FLAG_DATA_IS_HOMED);
        DODX( (dbuf->UnlockRect()), dx_draw_pixmap );

        SAFE_RELEASE(sbuf);
        SAFE_RELEASE(dbuf);


        // Copy from temp to visuals[bufnr]
        RECT src2Rect = { 0, 0, dest.xl, dest.yl };
        POINT dst2Point = { dest.x, dest.y };

        srct = temp;
        dstt = dx_state->visuals[bufnr];
        DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
        DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
        DODX( (dx_state->g_pd3dDevice->CopyRects(sbuf, &src2Rect, 1, dbuf, 
&dst2Point)), dx_draw_pixmap );

        SAFE_RELEASE(sbuf);
        SAFE_RELEASE(dbuf);
        SAFE_RELEASE(temp);

        return GFX_OK;
}


/* Grabs an image from the visual or priority back buffer. */
static int
dx_grab_pixmap(struct _gfx_driver *drv, rect_t src, gfx_pixmap_t *pxm,
                      gfx_map_mask_t map)
{
        HRESULT hr;

        if (src.x < 0 || src.y < 0) {
                GFXERROR("Attempt to grab pixmap from invalid coordinates 
(%d,%d)\n", src.x, src.y);
                return GFX_ERROR;
        }

        if (!pxm->data) {
                GFXERROR("Attempt to grab pixmap to unallocated memory\n");
                return GFX_ERROR;
        }

        // Choose map to grab from
        switch (map) {

        case GFX_MASK_VISUAL: {
                LPDIRECT3DTEXTURE8 temp;
                LPDIRECT3DSURFACE8 tempSrf, backSrf;
                CONST RECT srcRect = RECT_T_TO_RECT(src);
                CONST POINT dstPoint = { 0, 0 };

                pxm->xl = src.xl;
                pxm->yl = src.yl;

                DODX( (dx_state->g_pd3dDevice->CreateTexture(pxm->xl, pxm->yl, 
1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &temp)), dx_grab_pixmap );

                DODX( (dx_state->visuals[BACK_VIS]->GetSurfaceLevel(0, 
&backSrf)), dx_grab_pixmap );
                DODX( (temp->GetSurfaceLevel(0, &tempSrf)), dx_grab_pixmap );
                DODX( (dx_state->g_pd3dDevice->CopyRects(backSrf, &srcRect, 1, 
tempSrf, &dstPoint)), dx_grab_pixmap );

                pxm->internal.info = temp;
                pxm->internal.handle = SCI_DX_HANDLE_GRABBED;
                pxm->flags |= GFX_PIXMAP_FLAG_INSTALLED | 
GFX_PIXMAP_FLAG_EXTERNAL_PALETTE | GFX_PIXMAP_FLAG_PALETTE_SET;

                SAFE_RELEASE(backSrf);
                SAFE_RELEASE(tempSrf);

                break;
        }

        case GFX_MASK_PRIORITY:
                sciprintf("FIXME: priority map grab not implemented yet!\n");
                break;

        default:
                sciprintf("Attempt to grab pixmap from invalid map 0x%02x\n", 
map);
                return GFX_ERROR;
        }

        return GFX_OK;
}


/*** Buffer operations ***/

/* Updates the front buffer or the back buffers. */
static int
dx_update(struct _gfx_driver *drv, rect_t src, point_t dest, gfx_buffer_t 
buffer)
{
        HRESULT hr;
        LPDIRECT3DTEXTURE8 srct, dstt;
        LPDIRECT3DSURFACE8 sbuf, dbuf;
        CONST RECT srcRect = RECT_T_TO_RECT(src);
        CONST POINT dstPoint = { dest.x, dest.y };

        switch (buffer) {

        case GFX_BUFFER_FRONT:
                srct = dx_state->visuals[BACK_VIS];
                dstt = dx_state->visuals[PRIMARY_VIS];

                DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_update );
                DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_update );

                DODX( (dx_state->g_pd3dDevice->CopyRects(sbuf, &srcRect, 1, 
dbuf, &dstPoint)), dx_update );

                SAFE_RELEASE(sbuf);
                SAFE_RELEASE(dbuf);

                Render(drv);
                break;

        case GFX_BUFFER_BACK:
                if (src.x == dest.x && src.y == dest.y)
                        
gfx_copy_pixmap_box_i(dx_state->priority_maps[BACK_PRI], 
dx_state->priority_maps[STATIC_PRI], src);

                srct = dx_state->visuals[STATIC_VIS];
                dstt = dx_state->visuals[BACK_VIS];

                DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_update );
                DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_update );

                DODX( (dx_state->g_pd3dDevice->CopyRects(sbuf, &srcRect, 1, 
dbuf, &dstPoint)), dx_update );

                SAFE_RELEASE(sbuf);
                SAFE_RELEASE(dbuf);

                break;

        default:
                GFXERROR("Invalid buffer %d in update!\n", buffer);
                return GFX_ERROR;
        }

        return GFX_OK;
}

/* Sets the contents of the static visual and priority buffers. */
static int
dx_set_static_buffer(struct _gfx_driver *drv, gfx_pixmap_t *pic,
                            gfx_pixmap_t *priority)
{
        if (!pic->internal.info) {
                GFXERROR("Attempt to set static buffer with unregistered 
pixmap!\n");
                return GFX_ERROR;
        }

        HRESULT hr;
        LPDIRECT3DTEXTURE8 pii = (LPDIRECT3DTEXTURE8) (pic->internal.info);
        LPDIRECT3DSURFACE8 pbf;
        LPDIRECT3DTEXTURE8 vis = dx_state->visuals[STATIC_VIS];
        LPDIRECT3DSURFACE8 vbf;

        // Copy from pic to visual[static]
        DODX( (pii->GetSurfaceLevel(0, &pbf)), dx_set_static_buffer );
        DODX( (vis->GetSurfaceLevel(0, &vbf)), dx_set_static_buffer );
        DODX( (dx_state->g_pd3dDevice->CopyRects(pbf, NULL, 0, vbf, NULL)), 
dx_set_static_buffer );

        SAFE_RELEASE(pbf);
        SAFE_RELEASE(vbf);

        // Copy priority map
        gfx_copy_pixmap_box_i(dx_state->priority_maps[STATIC_PRI], priority, 
gfx_rect(0, 0, dx_state->xsize, dx_state->ysize));

        return GFX_OK;
}


/*** Mouse pointer operations ***/

/* Sets a new mouse pointer. */
static int
dx_set_pointer(struct _gfx_driver *drv, gfx_pixmap_t *pointer)
{
        return GFX_OK;
}


/*** Event management ***/

static sci_event_t
get_queue_event(gfx_dx_struct_t *ctx)
{
        if (ctx->queue_first == ctx->queue_size)
                ctx->queue_first = 0;

        if (ctx->queue_first == ctx->queue_last)
        {
                sci_event_t noevent;
                noevent.data = 0;
                noevent.type = SCI_EVT_NONE;
                noevent.buckybits = 0;
                return noevent;
        }
        else
                return ctx->event_queue [ctx->queue_first++];
}

static void
add_queue_event(gfx_dx_struct_t *ctx, int type, int data, short buckybits)
{
        if ((ctx->queue_last+1) % ctx->queue_size == ctx->queue_first)
        {
                /* Reallocate queue */
                int i, event_count;
                sci_event_t *new_queue;

                new_queue = (sci_event_t *) sci_malloc (ctx->queue_size * 2 * 
sizeof (sci_event_t));
                event_count = (ctx->queue_last - ctx->queue_first) % 
ctx->queue_size;
                for (i=0; i<event_count; i++)
                        new_queue [i] = ctx->event_queue [(ctx->queue_first+i) 
% ctx->queue_size];
                free (ctx->event_queue);
                ctx->event_queue = new_queue;
                ctx->queue_size *= 2;
                ctx->queue_first = 0;
                ctx->queue_last  = event_count;
        }

        ctx->event_queue [ctx->queue_last].data = data;
        ctx->event_queue [ctx->queue_last].type = type;
        ctx->event_queue [ctx->queue_last++].buckybits = buckybits;
        if (ctx->queue_last == ctx->queue_size)
                ctx->queue_last=0;
}

/* Returns the next event in the event queue for this driver. */
static sci_event_t
dx_get_event(struct _gfx_driver *drv)
{
        assert(drv->state != NULL);
        return get_queue_event(dx_state);
}


/* Sleeps the specified amount of microseconds, or until the mouse moves. */
static int
dx_usleep(struct _gfx_driver *drv, long usecs)
{
        if (usecs < 1000)
        {
                sleep(0);
        } else {
                sleep(usecs/1000);
        }
        ProcessMessages(drv);
        return GFX_OK;
}

static short
calc_buckybits()
{
        short buckybits = 0;

        if (GetAsyncKeyState (VK_RSHIFT))
                buckybits |= SCI_EVM_RSHIFT;
        if (GetAsyncKeyState (VK_LSHIFT))
                buckybits |= SCI_EVM_LSHIFT;
        if (GetAsyncKeyState (VK_CONTROL))
                buckybits |= SCI_EVM_CTRL;
        if (GetKeyState (VK_MENU))
                buckybits |= SCI_EVM_ALT;
        if (GetKeyState (VK_SCROLL) & 1)
                buckybits |= SCI_EVM_SCRLOCK;
        if (GetKeyState (VK_NUMLOCK) & 1)
                buckybits |= SCI_EVM_NUMLOCK;
        if (GetKeyState (VK_CAPITAL) & 1)
                buckybits |= SCI_EVM_CAPSLOCK;

        return buckybits;
}

static int
add_key_event (struct _gfx_driver *drv, int data)
{
        short buckybits = calc_buckybits();
        add_queue_event (dx_state, SCI_EVT_KEYBOARD, data, buckybits);

        return 0;
}

static void
update_mousepos(struct _gfx_driver *drv, MSG mouseMsg)
{
        if( ((mouseMsg.lParam & 0xFFFF) >= 320*dx_state->xfact) || 
((mouseMsg.lParam >> 16) >= 200*dx_state->yfact))
        {}
        else
        {
                drv->pointer_x = GET_X_LPARAM(mouseMsg.lParam);
                drv->pointer_y = GET_Y_LPARAM(mouseMsg.lParam);
        }

        if ( (dx_state->showWinCursor) && (!dx_state->showDXCursor) )
        {
                ShowCursor(TRUE);
                dx_state->g_pd3dDevice->ShowCursor(FALSE);
        }
        else if ( (!dx_state->showWinCursor) && (dx_state->showDXCursor) )
        {
                ShowCursor(FALSE);
                dx_state->g_pd3dDevice->ShowCursor(TRUE);
        }
        else
        {
                ShowCursor(FALSE);
                dx_state->g_pd3dDevice->ShowCursor(FALSE);
        }
}

static int
add_mouse_event(struct _gfx_driver *drv, int type, int data, MSG mMsg)
{
        update_mousepos(drv, mMsg);

        short buckybits = calc_buckybits();

        add_queue_event (dx_state, type, data, buckybits);

        return 0;
}


/* Check for Windows messages. */
static int
ProcessMessages(struct _gfx_driver *drv)
{
        MSG msg;

        while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
                switch( msg.message )
                {
                        case WM_PAINT:
                                ValidateRect( dx_state->hWnd, NULL );
                                Render(drv);
                                break;

                        case WM_KEYDOWN:
                                switch (msg.wParam)
                                {
                                        MAP_KEY (VK_ESCAPE,             
SCI_K_ESC);
                                        MAP_KEY (VK_BACK,               
SCI_K_BACKSPACE);
                                        MAP_KEY (VK_RETURN,             
SCI_K_ENTER);
                                        MAP_KEY (VK_TAB,                
SCI_K_TAB);
                                        MAP_KEY (VK_END,                
SCI_K_END);
                                        MAP_KEY (VK_DOWN,               
SCI_K_DOWN);
                                        MAP_KEY (VK_NEXT,               
SCI_K_PGDOWN);
                                        MAP_KEY (VK_LEFT,               
SCI_K_LEFT);
                                        MAP_KEY (VK_RIGHT,              
SCI_K_RIGHT);
                                        MAP_KEY (VK_HOME,               
SCI_K_HOME);
                                        MAP_KEY (VK_UP,                 
SCI_K_UP);
                                        MAP_KEY (VK_PRIOR,              
SCI_K_PGUP);
                                        MAP_KEY (VK_INSERT,             
SCI_K_INSERT);
                                        MAP_KEY (VK_DELETE,             
SCI_K_DELETE);
                                        MAP_KEY (VK_DECIMAL,    SCI_K_DELETE);
                                        MAP_KEY (VK_ADD,                
SCI_K_PLUS);
                                        MAP_KEY (VK_OEM_PLUS,   SCI_K_EQUALS);
                                        MAP_KEY (VK_SUBTRACT,   SCI_K_MINUS);
                                        MAP_KEY (VK_OEM_MINUS,  SCI_K_MINUS);
                                        MAP_KEY (VK_MULTIPLY,   SCI_K_MULTIPLY);
                                        MAP_KEY (VK_DIVIDE,             
SCI_K_DIVIDE);
                                        MAP_KEY (VK_OEM_COMMA,  ',');
                                        MAP_KEY (VK_OEM_PERIOD, '.');
                                        MAP_KEY (VK_OEM_1,              ';');   
// US keyboards only
                                        MAP_KEY (VK_OEM_2,              '/');
                                        MAP_KEY (VK_OEM_3,              '`');
                                        MAP_KEY (VK_OEM_4,              '[');
                                        MAP_KEY (VK_OEM_5,              '\\');
                                        MAP_KEY (VK_OEM_6,              ']');
                                        MAP_KEY (VK_OEM_7,              '\'');
                                        MAP_KEY (VK_F1,                 
SCI_K_F1);
                                        MAP_KEY (VK_F2,                 
SCI_K_F2);
                                        MAP_KEY (VK_F3,                 
SCI_K_F3);
                                        MAP_KEY (VK_F4,                 
SCI_K_F4);
                                        MAP_KEY (VK_F5,                 
SCI_K_F5);
                                        MAP_KEY (VK_F6,                 
SCI_K_F6);
                                        MAP_KEY (VK_F7,                 
SCI_K_F7);
                                        MAP_KEY (VK_F8,                 
SCI_K_F8);
                                        MAP_KEY (VK_F9,                 
SCI_K_F9);
                                        MAP_KEY (VK_F10,                
SCI_K_F10);

                                        case VK_RSHIFT:
                                        case VK_LSHIFT:
                                        case VK_CONTROL:
                                        case VK_MENU:
                                        case VK_SCROLL:
                                        case VK_NUMLOCK:
                                        case VK_CAPITAL:
                                                break;  // ignore

                                default:
                                        if (msg.wParam >= 'A' && msg.wParam <= 
'Z')
                                                add_key_event (drv, msg.wParam 
- 'A' + 97);
                                        else if (msg.wParam >= VK_NUMPAD0 && 
msg.wParam <= VK_NUMPAD9)
                                        {
                                                if (GetKeyState (VK_NUMLOCK) & 
1)
                                                        add_key_event (drv, 
msg.wParam - VK_NUMPAD0 + '0');
                                                else
                                                switch (msg.wParam)
                                                {
                                                        MAP_KEY (VK_NUMPAD0, 
SCI_K_INSERT);
                                                        MAP_KEY (VK_NUMPAD1, 
SCI_K_END);
                                                        MAP_KEY (VK_NUMPAD2, 
SCI_K_DOWN);
                                                        MAP_KEY (VK_NUMPAD3, 
SCI_K_PGDOWN);
                                                        MAP_KEY (VK_NUMPAD4, 
SCI_K_LEFT);
                                                        MAP_KEY (VK_NUMPAD6, 
SCI_K_RIGHT);
                                                        MAP_KEY (VK_NUMPAD7, 
SCI_K_HOME);
                                                        MAP_KEY (VK_NUMPAD8, 
SCI_K_UP);
                                                        MAP_KEY (VK_NUMPAD9, 
SCI_K_PGUP);
                                                }
                                        }
                                        else if (msg.wParam == 0xC0)  // tilde 
key - used for invoking console
                                                add_key_event (drv, '`');
                                        else
                                                add_key_event (drv, msg.wParam);
                                        break;
                                }
                                break;


                        case WM_LBUTTONDOWN: add_mouse_event (drv, 
SCI_EVT_MOUSE_PRESS, 1, msg);   break;
                        case WM_RBUTTONDOWN: add_mouse_event (drv, 
SCI_EVT_MOUSE_PRESS, 2, msg);   break;
                        case WM_MBUTTONDOWN: add_mouse_event (drv, 
SCI_EVT_MOUSE_PRESS, 3, msg);   break;
                        case WM_LBUTTONUP:   add_mouse_event (drv, 
SCI_EVT_MOUSE_RELEASE, 1, msg); break;
                        case WM_RBUTTONUP:   add_mouse_event (drv, 
SCI_EVT_MOUSE_RELEASE, 2, msg); break;
                        case WM_MBUTTONUP:   add_mouse_event (drv, 
SCI_EVT_MOUSE_RELEASE, 3, msg); break;

                        case WM_SETCURSOR:
                                // If is minimised or should show pointer
                                if(IsIconic(dx_state->hWnd) || 
dx_state->showWinCursor)
                                {
                                        SetCursor(LoadCursor (NULL, IDC_ARROW));
                                }
                                else
                                {
                                        SetCursor(NULL);
                                }
                                break;

                        case WM_NCMOUSEMOVE:
                                // Mouse moved in non-client area of window
                                dx_state->showWinCursor = TRUE;
                                dx_state->showDXCursor = FALSE;
                                break;

                        case WM_MOUSEMOVE:
                                dx_state->showWinCursor = FALSE;
                                dx_state->showDXCursor = TRUE;
                                update_mousepos(drv, msg);
                                break;

                        case WM_DESTROY:
                                PostQuitMessage( 0 );
                                drv->exit(drv);
                                exit(-1);
                                break;

                }
        }

        return TRUE;
}


extern "C"
gfx_driver_t gfx_driver_dx = {
        "directx",
        "0.3",
        SCI_GFX_DRIVER_MAGIC,
        SCI_GFX_DRIVER_VERSION,
        NULL,   /* mode */
        0, 0,   /* mouse pointer position */
        GFX_CAPABILITY_MOUSE_SUPPORT | GFX_CAPABILITY_FINE_LINES |
        GFX_CAPABILITY_PIXMAP_REGISTRY | GFX_CAPABILITY_POINTER_PIXMAP_REGISTRY 
|
        GFX_CAPABILITY_PIXMAP_GRABBING,
        0,
        dx_set_param,
        dx_init_specific,
        dx_init,
        dx_exit,
        dx_draw_line,
        dx_draw_filled_rect,
        dx_register_pixmap,
        dx_unregister_pixmap,
        dx_draw_pixmap,
        dx_grab_pixmap,
        dx_update,
        dx_set_static_buffer,
        NULL,//dx_set_pointer,
        NULL,
        dx_get_event,
        dx_usleep,
        NULL
};

#endif


reply via email to

[Prev in Thread] Current Thread [Next in Thread]