shithub: duke3d

ref: 46ce50a988adf43ed85367e42fe252207c61c918
dir: /Engine/src/display.c/

View raw version
/*
 * An SDL replacement for BUILD's VESA code.
 *
 *  Written by Ryan C. Gordon. (icculus@clutteredmind.org)
 *
 * Please do NOT harrass Ken Silverman about any code modifications
 *  (including this file) to BUILD.
 */

/*
 * "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
 * Ken Silverman's official web site: "http://www.advsys.net/ken"
 * See the included license file "BUILDLIC.TXT" for license info.
 * This file IS NOT A PART OF Ken Silverman's original release
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "platform.h"

#define BUILD_NOMOUSEGRAB    "BUILD_NOMOUSEGRAB"
#define BUILD_WINDOWED       "BUILD_WINDOWED"
#define BUILD_SDLDEBUG       "BUILD_SDLDEBUG"
#define BUILD_RENDERER       "BUILD_RENDERER"
#define BUILD_GLLIBRARY      "BUILD_GLLIBRARY"
#define BUILD_USERSCREENRES  "BUILD_USERSCREENRES"
#define BUILD_MAXSCREENRES   "BUILD_MAXSCREENRES"
#define BUILD_HALLOFMIRRORS  "BUILD_HALLOFMIRRORS"
#define BUILD_GLDUMP         "BUILD_GLDUMP"
#define BUILD_SDLJOYSTICK    "BUILD_SDLJOYSTICK"

#include "build.h"
#include "display.h"
#include "fixedPoint_math.h"
#include "engine.h"
#include "network.h"
#include "sdl_util.h"
#include "icon.h"
#include "draw.h"
#include "cache.h"


/*
 * !!! remove the surface_end checks, for speed's sake. They are a
 * !!!  needed safety right now. --ryan.
 */


#define DEFAULT_MAXRESWIDTH  MAXXDIM
#define DEFAULT_MAXRESHEIGHT MAXYDIM


#define UNLOCK_SURFACE_AND_RETURN  if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface); return;


    /* !!! move these elsewhere? */
int32_t xres, yres, bytesperline, imageSize, maxpages;
uint8_t* frameplace;

//The frambuffer address
uint8_t* frameoffset;
uint8_t  *screen, vesachecked;
int32_t buffermode, origbuffermode, linearmode;
uint8_t  permanentupdate = 0, vgacompatible;

static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
static SDL_Texture* texture = NULL;
static SDL_Surface* surface = NULL;
static SDL_Surface* surface_rgba = NULL;

static uint32_t sdl_flags = 0;
static int32_t mouse_relative_x = 0;
static int32_t mouse_relative_y = 0;
static short mouse_buttons = 0;
static unsigned int lastkey = 0;
/* so we can make use of setcolor16()... - DDOI */
static uint8_t  drawpixel_color=0;

static uint32_t scancodes[SDL_NUM_SCANCODES];

static int32_t last_render_ticks = 0;
int32_t total_render_time = 1;
int32_t total_rendered_frames = 0;

static char *titleName = NULL;

#define print_tf_state(str, val) printf("%s: {%s}\n", str, (val) ? "true" : "false" )

void* get_framebuffer(void){
    return((Uint8 *) surface->pixels);
}

/*
 * !!! This is almost an entire copy of the original setgamemode().
 * !!!  Figure out what is needed for just 2D mode, and separate that
 * !!!  out. Then, place the original setgamemode() back into engine.c,
 * !!!  and remove our simple implementation (and this function.)
 * !!!  Just be sure to keep the non-DOS things, like the window's
 * !!!  titlebar caption.   --ryan.
 */
static uint8_t  screenalloctype = 255;
static void init_new_res_vars()
{
    int i = 0;
    int j = 0;

    setupmouse();

    xdim = xres = surface->w;
    ydim = yres = surface->h;

	printf("init_new_res_vars %d %d\n",xdim,ydim);

    bytesperline = surface->w;
    vesachecked = 1;
    vgacompatible = 1;
    linearmode = 1;
	qsetmode = surface->h;
	activepage = visualpage = 0;

     
    frameoffset = frameplace = (uint8_t*)surface->pixels;

  	if (screen != NULL)
   	{
       	if (screenalloctype == 0) free((void *)screen);
   	    if (screenalloctype == 1) suckcache((int32_t *)screen);
   		screen = NULL;
   	} /* if */


    	switch(vidoption)
    	{
    		case 1:i = xdim*ydim; break;
    		case 2: xdim = 320; ydim = 200; i = xdim*ydim; break;
    		
    		default: assert(0);
    	}
    	j = ydim*4*sizeof(int32_t);  /* Leave room for horizlookup&horizlookup2 */

		if(horizlookup)
			free(horizlookup);

		if(horizlookup2)
			free(horizlookup2);
		
		horizlookup = (int32_t*)malloc(j);
		horizlookup2 = (int32_t*)malloc(j);

    j = 0;
    
    //Build lookup table (X screespace -> frambuffer offset. 
  	for(i = 0; i <= ydim; i++)
    {
        ylookup[i] = j;
        j += bytesperline;
    }

   	horizycent = ((ydim*4)>>1);

    /* Force drawrooms to call dosetaspect & recalculate stuff */
	oxyaspect = oxdimen = oviewingrange = -1;

    //Let the Assembly module how many pixels to skip when drawing a column
	setBytesPerLine(bytesperline);

    
    setview(0L,0L,xdim-1,ydim-1);
    
	setbrightness(curbrightness, palette);

	if (searchx < 0) {
        searchx = halfxdimen;
        searchy = (ydimen>>1);
    }    
}

static void go_to_new_vid_mode(int w, int h)
{
    if (window != NULL)
    {
        SDL_FreeSurface(surface_rgba);
        SDL_FreeSurface(surface);
        SDL_DestroyTexture(texture);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_QuitSubSystem(SDL_INIT_VIDEO);
        window = NULL;
    }

    SDL_CHECK_SUCCESS( SDL_InitSubSystem(SDL_INIT_VIDEO) );

    window = SDL_CreateWindow(
        titleName,
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        w, h,
        sdl_flags | SDL_WINDOW_ALLOW_HIGHDPI);

    SDL_CHECK_NOT_NULL(window, "create window");

    // don't override higher-res app icon on OS X or Windows
#if !PLATFORM_MACOSX && !WIN32
    SDL_Surface* image = SDL_LoadBMP_RW(SDL_RWFromMem(iconBMP, sizeof(iconBMP)), 1);
    Uint32 colorkey = 0; // index in this image to be transparent
    SDL_SetColorKey(image, SDL_TRUE, colorkey);
    SDL_SetWindowIcon(window, image);
    SDL_FreeSurface(image);
#endif
	
    getvalidvesamodes();
    SDL_ClearError();

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    SDL_CHECK_NOT_NULL(renderer, "create renderer");

    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "0");
	
    SDL_CHECK_SUCCESS( SDL_RenderSetLogicalSize(renderer, w, h) );

    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, w, h);
    SDL_CHECK_NOT_NULL(texture, "create texture");
	
    surface = SDL_CreateRGBSurface(0, w, h, 8, 0, 0, 0, 0);
    SDL_CHECK_NOT_NULL(surface, "create palettized surface");

    const Uint32 rmask = 0x00ff0000;
    const Uint32 gmask = 0x0000ff00;
    const Uint32 bmask = 0x000000ff;
    const Uint32 amask = 0xff000000;

    surface_rgba = SDL_CreateRGBSurface(0, w, h, 32, rmask, gmask, bmask, amask);
    SDL_CHECK_NOT_NULL(surface_rgba, "create RGBA surface");

    init_new_res_vars();
}

static inline int sdl_mouse_button_filter(SDL_MouseButtonEvent const *event)
{
        /*
         * What bits BUILD expects:
         *  0 left button pressed if 1
         *  1 right button pressed if 1
         *  2 middle button pressed if 1
         *
         *   (That is, this is what Int 33h (AX=0x05) returns...)
         *
         *  additionally bits 3&4 are set for the mouse wheel
         */
    Uint8 button = event->button;
    if (button >= sizeof (mouse_buttons) * 8)
        return(0);

    if (button == SDL_BUTTON_RIGHT)
        button = SDL_BUTTON_MIDDLE;
    else if (button == SDL_BUTTON_MIDDLE)
        button = SDL_BUTTON_RIGHT;

    if (((const SDL_MouseButtonEvent*)event)->state)
        mouse_buttons |= 1<<(button-1);
    else if (button != 4 && button != 5)
        mouse_buttons ^= 1<<(button-1);
#if 0
    Uint8 bmask = SDL_GetMouseState(NULL, NULL);
    mouse_buttons = 0;
    if (bmask & SDL_BUTTON_LMASK) mouse_buttons |= 1;
    if (bmask & SDL_BUTTON_RMASK) mouse_buttons |= 2;
    if (bmask & SDL_BUTTON_MMASK) mouse_buttons |= 4;
#endif

    return(0);
} /* sdl_mouse_up_filter */


static int sdl_mouse_motion_filter(SDL_Event const *event)
{
    if (surface == NULL)
		return(0);

    if (event->type == SDL_JOYBALLMOTION)
    {
        mouse_relative_x = event->jball.xrel/100;
        mouse_relative_y = event->jball.yrel/100;
    }
    else
    {
        if (SDL_GetRelativeMouseMode() == SDL_TRUE)
        {
            mouse_relative_x += event->motion.xrel;
            mouse_relative_y += event->motion.yrel;
            //printf("sdl_mouse_motion_filter: mrx=%d, mry=%d, mx=%d, my=%d\n",
            //	mouse_relative_x, mouse_relative_y, event->motion.xrel, event->motion.yrel);

            // mouse_relative_* is already reset in 
            // readmousexy(). It must not be
            // reset here because calling this function does not mean
            // we always handle the mouse. 
            // FIX_00001: Mouse speed is uneven and slower in windowed mode vs fullscreen mode.
        }
        else
            mouse_relative_x = mouse_relative_y = 0;
    }

    return(0);
} /* sdl_mouse_motion_filter */


    /*
     * The windib driver can't alert us to the keypad enter key, which
     *  Ken's code depends on heavily. It sends it as the same key as the
     *  regular return key. These users will have to hit SHIFT-ENTER,
     *  which we check for explicitly, and give the engine a keypad enter
     *  enter event.
     */
static inline int handle_keypad_enter_hack(const SDL_Event *event)
{
    static int kp_enter_hack = 0;
    int retval = 0;

    if (event->key.keysym.sym == SDLK_RETURN)
    {
        if (event->key.state == SDL_PRESSED)
        {
            if (event->key.keysym.mod & KMOD_SHIFT)
            {
                kp_enter_hack = 1;
                lastkey = SDL_SCANCODE_KP_ENTER;
                retval = 1;
            } /* if */
        } /* if */

        else  /* key released */
        {
            if (kp_enter_hack)
            {
                kp_enter_hack = 0;
                lastkey = SDL_SCANCODE_KP_ENTER;
                retval = 1;
            } /* if */
        } /* if */
    } /* if */

    return(retval);
} /* handle_keypad_enter_hack */

void fullscreen_toggle_and_change_driver(void)
{
	
//  FIX_00002: New Toggle Windowed/FullScreen system now simpler and will 
//  dynamically change for Windib or Directx driver. Windowed/Fullscreen 
//  toggle also made available from menu.
//  Replace attempt_fullscreen_toggle(SDL_Surface **surface, Uint32 *flags)
  	
	int32_t x,y;
	x = surface->w;
	y = surface->h;

	BFullScreen =!BFullScreen;
	_platform_init(0, NULL, "Duke Nukem 3D", "Duke3D");
	_setgamemode(x,y);
	//vscrn();

	return;
}

static int sdl_key_filter(const SDL_Event *event)
{
    int extended;

    if ( (event->key.keysym.sym == SDLK_m) &&
         (event->key.state == SDL_PRESSED) &&
         (event->key.keysym.mod & KMOD_CTRL) )
    {
		// FIX_00005: Mouse pointer can be toggled on/off (see mouse menu or use CTRL-M)
		// This is usefull to move the duke window when playing in window mode.
  
        if (SDL_GetRelativeMouseMode() == SDL_TRUE) 
		{
            SDL_CHECK_SUCCESS( SDL_SetRelativeMouseMode(SDL_FALSE) );
		}
        else
        {
            SDL_CHECK_SUCCESS( SDL_SetRelativeMouseMode(SDL_TRUE) );
        }

        return(0);
    }

    if (((event->key.keysym.sym == SDLK_RETURN) ||
        (event->key.keysym.sym == SDLK_KP_ENTER)) &&
        (event->key.state == SDL_PRESSED) &&
        (event->key.keysym.mod & KMOD_ALT))
    {
        fullscreen_toggle_and_change_driver();

        // hack to discard the ALT key...
        lastkey = scancodes[SDL_SCANCODE_RALT] >> 8; // extended
        keyhandler();
        lastkey = (scancodes[SDL_SCANCODE_RALT] & 0xff) + 0x80; // Simulating Key up
        keyhandler();
        lastkey = (scancodes[SDL_SCANCODE_LALT] & 0xff) + 0x80; // Simulating Key up (not extended)
        keyhandler();
        SDL_SetModState(KMOD_NONE); // SDL doesnt see we are releasing the ALT-ENTER keys

        return(0);
    }

    if (!handle_keypad_enter_hack(event))
        lastkey = scancodes[event->key.keysym.scancode];

//	printf("key.keysym.sym=%d\n", event->key.keysym.sym);

    if (lastkey == 0x0000)   /* No DOS equivalent defined. */
        return(0);

    extended = ((lastkey & 0xFF00) >> 8);
    if (extended != 0)
    {
        lastkey = extended;
        keyhandler();
        lastkey = (scancodes[event->key.keysym.scancode] & 0xFF);
    } /* if */

    if (event->key.state == SDL_RELEASED)
        lastkey += 128;  /* +128 signifies that the key is released in DOS. */

    keyhandler();
    return(0);
} /* sdl_key_filter */


static int root_sdl_event_filter(const SDL_Event *event)
{
    switch (event->type)
    {
        case SDL_KEYUP:
            // FIX_00003: Pause mode is now fully responsive - (Thx to Jonathon Fowler tips)
			if(event->key.keysym.sym == SDLK_PAUSE)
				break;
        case SDL_KEYDOWN:
            return(sdl_key_filter(event));
        case SDL_JOYBUTTONDOWN:
        case SDL_JOYBUTTONUP:
            {
                //Do Nothing

                //printf("Joybutton UP/DOWN\n");
	            //return(sdl_joystick_button_filter((const SDL_MouseButtonEvent*)event));
                return 0;
            }
        case SDL_JOYBALLMOTION:
        case SDL_MOUSEMOTION:
            return(sdl_mouse_motion_filter(event));
        case SDL_MOUSEBUTTONUP:
        case SDL_MOUSEBUTTONDOWN:
			return(sdl_mouse_button_filter((const SDL_MouseButtonEvent*)event));
        case SDL_QUIT:
            /* !!! rcg TEMP */
            Error(EXIT_SUCCESS, "Exit through SDL\n"); 
		default:
			//printf("This event is not handled: %d\n",event->type);
			break;
    } /* switch */

    return(1);
} /* root_sdl_event_filter */


static void handle_events(void)
{
    SDL_Event event;

	while(SDL_PollEvent(&event))
        root_sdl_event_filter(&event);
} /* handle_events */


/* bleh...public version... */
void _handle_events(void)
{
    handle_events();
} /* _handle_events */


static SDL_Joystick *joystick = NULL;
void _joystick_init(void)
{
    const char  *envr = getenv(BUILD_SDLJOYSTICK);
    int favored = 0;
    int numsticks;
    int i;

    if (joystick != NULL)
    {
        printf("Joystick appears to be already initialized.\n");
        printf("...deinitializing for stick redetection...\n");
        _joystick_deinit();
    } /* if */

    if ((envr != NULL) && (strcmp(envr, "none") == 0))
    {
        printf("Skipping joystick detection/initialization at user request\n");
        return;
    } /* if */

    printf("Initializing SDL joystick subsystem...");
    printf(" (export environment variable BUILD_SDLJOYSTICK=none to skip)\n");

    if (SDL_Init(SDL_INIT_JOYSTICK|SDL_INIT_NOPARACHUTE) != 0)
    {
        printf("SDL_Init(SDL_INIT_JOYSTICK) failed: [%s].\n", SDL_GetError());
        return;
    } /* if */

    numsticks = SDL_NumJoysticks();
    printf("SDL sees %d joystick%s.\n", numsticks, numsticks == 1 ? "" : "s");
    if (numsticks == 0)
        return;

    for (i = 0; i < numsticks; i++)
    {
        const char  *stickname = SDL_JoystickName(i);
        if ((envr != NULL) && (strcmp(envr, stickname) == 0))
            favored = i;

        printf("Stick #%d: [%s]\n", i, stickname);
    } /* for */

    printf("Using Stick #%d.", favored);
    if ((envr == NULL) && (numsticks > 1))
        printf("Set BUILD_SDLJOYSTICK to one of the above names to change.\n");

    joystick = SDL_JoystickOpen(favored);
    if (joystick == NULL)
    {
        printf("Joystick #%d failed to init: %s\n", favored, SDL_GetError());
        return;
    } /* if */

    printf("Joystick initialized. %d axes, %d buttons, %d hats, %d balls.\n",
              SDL_JoystickNumAxes(joystick), SDL_JoystickNumButtons(joystick),
              SDL_JoystickNumHats(joystick), SDL_JoystickNumBalls(joystick));

    SDL_JoystickEventState(SDL_QUERY);
} /* _joystick_init */


void _joystick_deinit(void)
{
    if (joystick != NULL)
    {
        printf("Closing joystick device...\n");
        SDL_JoystickClose(joystick);
        printf("Joystick device closed. Deinitializing SDL subsystem...\n");
        SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
        printf("SDL joystick subsystem deinitialized.\n");
        joystick = NULL;
    } /* if */
} /* _joystick_deinit */


int _joystick_update(void)
{
    if (joystick == NULL)
        return(0);

    SDL_JoystickUpdate();
    return(1);
} /* _joystick_update */


int _joystick_axis(int axis)
{
    if (joystick == NULL)
    {   
        return(0);
    }

    return(SDL_JoystickGetAxis(joystick, axis));
} /* _joystick_axis */

int _joystick_hat(int hat)
{
    if (joystick == NULL)
    {   
        return(-1);
    }

    return(SDL_JoystickGetHat(joystick, hat));
} /* _joystick_axis */

int _joystick_button(int button)
{
    if (joystick == NULL)
        return(0);

    return(SDL_JoystickGetButton(joystick, button) != 0);
} /* _joystick_button */


uint8_t  _readlastkeyhit(void)
{
    return(lastkey);
} /* _readlastkeyhit */



#if (!defined __DATE__)
#define __DATE__ "a long, int32_t time ago"
#endif

static void output_sdl_versions(void)
{
    SDL_version linked_ver;
    SDL_GetVersion(&linked_ver);

    SDL_version compiled_ver;
    SDL_VERSION(&compiled_ver);

    printf("SDL Display driver for the BUILD engine initializing.\n");
    printf("SDL Compiled %s against SDL version %d.%d.%d ...\n", __DATE__, compiled_ver.major, compiled_ver.minor, compiled_ver.patch);
    printf("SDL Linked against SDL version %d.%d.%d ...\n", linked_ver.major, linked_ver.minor, linked_ver.patch);
}

/* lousy -ansi flag.  :) */
static char  *string_dupe(const char  *str)
{
    char  *retval = malloc(strlen(str) + 1);
    if (retval != NULL)
        strcpy(retval, str);
    return(retval);
}

void _platform_init(int argc, char  **argv, const char  *title, const char  *iconName)
{
    int i;
	int64_t timeElapsed;

	// FIX_00061: "ERROR: Two players have the same random ID" too frequent cuz of internet windows times
    timeElapsed = SDL_GetTicks();
	srand(timeElapsed&0xFFFFFFFF);

	Setup_UnstableNetworking();

    // Look through the command line args
    for(i = 0; i < argc; i++)
    {
        if(argv[i][0] == '-' )
        {
            if(strcmpi(argv[i], "-netmode_stable") == 0)
            {
                //fullscreen = 1;
				//TODO:
//TODO ( "[Todo: handle -netmode <int>]" )
				Setup_StableNetworking();
					
            }
        }
    }    

#ifdef __APPLE__
    SDL_putenv("SDL_VIDEODRIVER=Quartz");
#endif  	

    SDL_CHECK_SUCCESS( SDL_Init(SDL_INIT_VIDEO) );
	
    if (title == NULL)
        title = "BUILD";

    titleName = string_dupe(title);
    sdl_flags = BFullScreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
    set_sdl_scancodes(scancodes, sizeof(scancodes) / sizeof(scancodes[0]));
    
    output_sdl_versions();

	printf("Video Driver: '%s'.\n", SDL_GetCurrentVideoDriver());
}

// Capture BMP of the current frame
int screencapture(char  *filename, uint8_t  inverseit)
{
//  FIX_00006: better naming system for screenshots + message when pic is taken. 
//  Use ./screenshots folder. Screenshot code rerwritten. Faster and
//  makes smaller files. Doesn't freeze or lag the game anymore.
  
	SDL_SaveBMP(surface, filename);  
	return 0;
} /* screencapture */


void setvmode(int mode)
{

    if (mode == 0x3)  /* text mode. */
    {
        SDL_QuitSubSystem(SDL_INIT_VIDEO);
        return;
    } else
        printf("setvmode(0x%x) is unsupported in SDL driver.\n", mode);

} 

int _setgamemode(int32_t daxdim, int32_t daydim)
{
	int validated, i;
    
    if (daxdim > MAXXDIM || daydim > MAXYDIM)
    {
		printf("Resolution %dx%d is too high. Changed to %dx%d\n", daxdim, daydim, MAXXDIM,MAXYDIM);
	    daxdim = MAXXDIM;
	    daydim = MAXYDIM;
    } 

	getvalidvesamodes();

	validated = 0;
	for(i=0; i<validmodecnt; i++){
		if(validmodexdim[i] == daxdim && validmodeydim[i] == daydim)
			validated = 1;
	}

	if(!validated){
		printf("Resolution %dx%d unsupported. Changed to 640x480\n", daxdim, daydim);
	    daxdim = 640;
	    daydim = 480;
    }

    go_to_new_vid_mode(daxdim, daydim);

    qsetmode = 200;
    last_render_ticks = getticks();

    return(0);
} /* setgamemode */


static int get_dimensions_from_str(const char  *str, int32_t *_w, int32_t *_h)
{
    char  *xptr = NULL;
    char  *ptr = NULL;
    int32_t w = -1;
    int32_t h = -1;

    if (str == NULL)
        return(0);

    xptr = strchr(str, 'x');
    if (xptr == NULL)
        return(0);

    w = strtol(str, &ptr, 10);
    if (ptr != xptr)
        return(0);

    xptr++;
    h = strtol(xptr, &ptr, 10);
    if ( (*xptr == '\0') || (*ptr != '\0') )
        return(0);

    if ((w <= 1) || (h <= 1))
        return(0);

    if (_w != NULL)
        *_w = w;

    if (_h != NULL)
        *_h = h;

    return(1);
} /* get_dimensions_from_str */


static inline void get_max_screen_res(int32_t *max_w, int32_t *max_h)
{
    int32_t w = DEFAULT_MAXRESWIDTH;
    int32_t h = DEFAULT_MAXRESHEIGHT;
    const char  *envr = getenv(BUILD_MAXSCREENRES);

    if (envr != NULL)
    {
        if (!get_dimensions_from_str(envr, &w, &h))
        {
            printf("User's resolution ceiling [%s] is bogus!\n", envr);
            w = DEFAULT_MAXRESWIDTH;
            h = DEFAULT_MAXRESHEIGHT;
        } /* if */
    } /* if */

    if (max_w != NULL)
        *max_w = w;

    if (max_h != NULL)
        *max_h = h;
}


static void add_vesa_mode(int w, int h)
{
    validmode[validmodecnt] = validmodecnt;
    validmodexdim[validmodecnt] = w;
    validmodeydim[validmodecnt] = h;
    validmodecnt++;
}

/* Let the user specify a specific mode via environment variable. */
static inline void add_user_defined_resolution(void)
{
    int32_t w;
    int32_t h;
    const char  *envr = getenv(BUILD_USERSCREENRES);

    if (envr == NULL)
        return;

    if (get_dimensions_from_str(envr, &w, &h))
        add_vesa_mode(w, h);
    else
        printf("User defined resolution [%s] is bogus!\n", envr);
} /* add_user_defined_resolution */

static void remove_vesa_mode(int index, const char  *reason)
{
    int i;

    assert(index < validmodecnt);
    //printf("Removing resolution #%d, %dx%d [%s].\n",index, validmodexdim[index], validmodeydim[index], reason);

    for (i = index; i < validmodecnt - 1; i++)
    {
        validmode[i] = validmode[i + 1];
        validmodexdim[i] = validmodexdim[i + 1];
        validmodeydim[i] = validmodeydim[i + 1];
    } /* for */

    validmodecnt--;
} /* remove_vesa_mode */


static inline void cull_large_vesa_modes(void)
{
    int32_t max_w;
    int32_t max_h;
    int i;
 
    get_max_screen_res(&max_w, &max_h);
    printf("Setting resolution ceiling to (%dx%d).\n", max_w, max_h);

    for (i = 0; i < validmodecnt; i++)
    {
        if ((validmodexdim[i] > max_w) || (validmodeydim[i] > max_h))
        {
            remove_vesa_mode(i, "above resolution ceiling");
            i--;  /* list shrinks. */
        } /* if */
    } /* for */
} /* cull_large_vesa_modes */


static inline void cull_duplicate_vesa_modes(void)
{
    int i;
    int j;

    for (i = 0; i < validmodecnt; i++){
        for (j = i + 1; j < validmodecnt; j++){
            if ( (validmodexdim[i] == validmodexdim[j]) &&(validmodeydim[i] == validmodeydim[j]) ){
                remove_vesa_mode(j, "duplicate");
                j--;  /* list shrinks. */
            }
        }
    }
} 


#define swap_macro(tmp, x, y) { tmp = x; x = y; y = tmp; }

/* be sure to call cull_duplicate_vesa_modes() before calling this. */
static inline void sort_vesa_modelist(void)
{
    int i;
    int sorted;
    int32_t tmp;

    do
    {
        sorted = 1;
        for (i = 0; i < validmodecnt - 1; i++)
        {
            if ( (validmodexdim[i] >= validmodexdim[i+1]) &&
                 (validmodeydim[i] >= validmodeydim[i+1]) )
            {
                sorted = 0;
                swap_macro(tmp, validmode[i], validmode[i+1]);
                swap_macro(tmp, validmodexdim[i], validmodexdim[i+1]);
                swap_macro(tmp, validmodeydim[i], validmodeydim[i+1]);
            } /* if */
        } /* for */
    } while (!sorted);
} /* sort_vesa_modelist */


static inline void cleanup_vesa_modelist(void)
{
    cull_large_vesa_modes();
    cull_duplicate_vesa_modes();
    sort_vesa_modelist();
} /* cleanup_vesa_modelist */


static inline void output_vesa_modelist(void)
{
    char  buffer[512];
    char  numbuf[20];
    int i;

    buffer[0] = '\0';

    for (i = 0; i < validmodecnt; i++)
    {
        sprintf(numbuf, " (%dx%d)",(int32_t) validmodexdim[i], (int32_t) validmodeydim[i]);

        if ( (strlen(buffer) + strlen(numbuf)) >= (sizeof (buffer) - 1) )
            strcpy(buffer + (sizeof (buffer) - 5), " ...");
        else
            strcat(buffer, numbuf);
    } /* for */

    printf("Final sorted modelist:%s\n", buffer);
} 


void getvalidvesamodes(void)
{
    static int already_checked = 0;
    int i;
    int stdres[][2] = {
	    {320, 240},
    	{640, 480},
    	{800, 600},
    	{1024, 768},
        {1280, 960},
    	{1440, 1080},
    	{1600, 1200},
		{1920, 1440},
        {2560, 1920},
		{2880, 2160}
    };

    if (already_checked)
        return;

    already_checked = 1;
   	validmodecnt = 0;
    vidoption = 1;  /* !!! tmp */
	
    // Fill in the standard 4:3 resolutions that the display supports
    int numModes = SDL_GetNumDisplayModes(0);
    int maxWidth = 0;
    int maxHeight = 0;
	
	for (i = 0; i < numModes; ++i)
	{
        SDL_DisplayMode mode;
        SDL_CHECK_SUCCESS(SDL_GetDisplayMode(0, i, &mode));

        maxWidth = max(maxWidth, mode.w);
        maxHeight = max(maxHeight, mode.h);
	}
	
    for (i = 0; i < sizeof(stdres) / sizeof(stdres[0]); i++)
    {
        assert(stdres[i][0] / 4 == stdres[i][1] / 3);
    	
        if (stdres[i][0] <= maxWidth && stdres[i][1] <= maxHeight)
        {
            add_vesa_mode(stdres[i][0], stdres[i][1]);
        }
    }
	
    /* Now add specific resolutions that the user wants... */
    add_user_defined_resolution();

    /* get rid of dupes and bogus resolutions... */
    cleanup_vesa_modelist();

    /* print it out for debugging purposes... */
    output_vesa_modelist();
} 

uint8_t lastPalette[768];
void WriteTranslucToFile(void)
{
    uint8_t buffer[65535*4];
    uint8_t tga_header[18];
    uint8_t* transPointer = transluc;
    uint8_t* bufferPointer = buffer;
    int i;
    FILE* file;
    
    for (i=0; i < 65535; i++) {
        
        bufferPointer[0] = (lastPalette[(*transPointer)*3+0]) / 63.0 * 255;
        bufferPointer[1] = (lastPalette[(*transPointer)*3+1]) / 63.0 * 255;
        bufferPointer[2] = (lastPalette[(*transPointer)*3+2]) / 63.0 * 255;
        bufferPointer[3] = 255;
        
        printf("%d,",*transPointer);
        if (i%255 ==0)
            printf("\n");
        
        transPointer +=1;
        bufferPointer+=4;
    }
    
    file = fopen("transluc.tga", "w");
    
    memset(tga_header, 0, 18);
    tga_header[2] = 2;
    tga_header[12] = (256 & 0x00FF);
    tga_header[13] = (256  & 0xFF00) / 256;
    tga_header[14] = (256  & 0x00FF) ;
    tga_header[15] =(256 & 0xFF00) / 256;
    tga_header[16] = 32 ;
    
    fwrite(&tga_header, 18, sizeof(uint8_t), file);
    fwrite(buffer, 65535, 4, file);
    fclose(file);
}

void WritePaletteToFile(uint8_t* palette,const char* filename,int width, int height)
{    
    uint8_t tga_header[18];
    uint8_t* buffer;
    uint8_t* palettePointer = palette;
    uint8_t* bufferPointer ;
    int i;
    
    FILE* file = fopen(filename, "w");

    memset(tga_header, 0, 18);
    tga_header[2] = 2;
    tga_header[12] = (width & 0x00FF);
    tga_header[13] = (width  & 0xFF00) / 256;
    tga_header[14] = (height  & 0x00FF) ;
    tga_header[15] =(height & 0xFF00) / 256;
    tga_header[16] = 32 ;
    
    fwrite(&tga_header, 18, sizeof(uint8_t), file);
    
    bufferPointer = buffer = malloc(width*height*4);
    
    for (i = 0 ; i < width*height ; i++)
    {
        bufferPointer[0] = palettePointer[0] / 63.0 * 255;
        bufferPointer[1] = palettePointer[1] / 63.0 * 255;
        bufferPointer[2] = palettePointer[2] / 63.0 * 255;
        bufferPointer[3] = 255;
        
        bufferPointer += 4;
        palettePointer+= 3;
    }
    
    fwrite(buffer, width*height, 4, file);
    fclose(file);
    
    free(buffer);
}


void WriteLastPaletteToFile()
{
    WritePaletteToFile(lastPalette,"lastPalette.tga",16,16);
}

void VBE_setPalette(uint8_t  *palettebuffer)
/*
 * (From Ken's docs:)
 *   Set (num) palette palette entries starting at (start)
 *   palette entries are in a 4-byte format in this order:
 *       0: Blue (0-63)
 *       1: Green (0-63)
 *       2: Red (0-63)
 *       3: Reserved
 *
 * Naturally, the bytes are in the reverse order that SDL wants them...
 *  More importantly, SDL wants the color elements in a range from 0-255,
 *  so we do a conversion.
 */
{
    SDL_Color fmt_swap[256];
    SDL_Color *sdlp = fmt_swap;
    uint8_t  *p = palettebuffer;
    int i;
    //static updated=0;
    
    //if (updated >=1 )
    //    return ;
    
    //WritePaletteToFile(palettebuffer,"lastPalette.tga",16,16);
    //updated++;
    
   
    //CODE EXPLORATION
    //Used only to write the last palette to file.
    memcpy(lastPalette, palettebuffer, 768);
    
    for (i = 0; i < 256; i++){
        sdlp->b = (Uint8) ((((float) *p++) / 63.0) * 255.0);
        sdlp->g = (Uint8) ((((float) *p++) / 63.0) * 255.0);
        sdlp->r = (Uint8) ((((float) *p++) / 63.0) * 255.0);
        sdlp->a = *p++;   /* This byte is unused in BUILD, too. */
        sdlp++;
    }

	// tanguyf: updating the palette is not immediate with a buffered surface, screen needs updating as well.
    SDL_CHECK_SUCCESS( SDL_SetPaletteColors(surface->format->palette, fmt_swap, 0, 256) );
    SDL_CHECK_SUCCESS( SDL_BlitSurface(surface, NULL, surface_rgba, NULL) );
    SDL_CHECK_SUCCESS( SDL_UpdateTexture(texture, NULL, surface_rgba->pixels, surface_rgba->pitch) );
    SDL_CHECK_SUCCESS( SDL_RenderCopy(renderer, texture, NULL, NULL) );
    SDL_RenderPresent(renderer);
}

void VBE_getPalette(int32_t start, int32_t num, uint8_t  *palettebuffer)
{
    SDL_Color *sdlp = surface->format->palette->colors + start;
    uint8_t  *p = palettebuffer + (start * 4);
    int i;

    for (i = 0; i < num; i++)
    {
        *p++ = (Uint8) ((((float) sdlp->b) / 255.0) * 63.0);
        *p++ = (Uint8) ((((float) sdlp->g) / 255.0) * 63.0);
        *p++ = (Uint8) ((((float) sdlp->r) / 255.0) * 63.0);
        *p++ = sdlp->a;   /* This byte is unused in both SDL and BUILD. */
        sdlp++;
    }
} 

void _uninitengine(void)
{
   SDL_QuitSubSystem(SDL_INIT_VIDEO);
} /* _uninitengine */

int setupmouse(void)
{

	SDL_Event event;

    if (surface == NULL)
        return(0);

    SDL_CHECK_SUCCESS( SDL_SetRelativeMouseMode(SDL_TRUE) );

    mouse_relative_x = mouse_relative_y = 0;

    /*
     * this global usually gets set by BUILD, but it's a one-shot
     *  deal, and we may not have an SDL surface at that point. --ryan.
     */
    moustat = 1;

	// FIX_00063: Duke's angle changing or incorrect when using toggle fullscreen/window mode
	while(SDL_PollEvent(&event)); // Empying the various pending events (especially the mouse one)

	//SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);

    return(1);
} /* setupmouse */


void readmousexy(short *x, short *y)
{
    if (x)
        *x = mouse_relative_x << 2;
    if (y)
        *y = mouse_relative_y << 2;

    mouse_relative_x = mouse_relative_y = 0;
} /* readmousexy */


void readmousebstatus(short *bstatus)
{
    if (bstatus)
        *bstatus = mouse_buttons;

    // special wheel treatment: make it like a button click
    if(mouse_buttons&8)
        mouse_buttons ^= 8;
    if(mouse_buttons&16)
        mouse_buttons ^= 16;

} /* readmousebstatus */


void _updateScreenRect(int32_t x, int32_t y, int32_t w, int32_t h)
{
    SDL_Rect rect = { x, y, w, h };

    SDL_CHECK_SUCCESS(SDL_BlitSurface(surface, &rect, surface_rgba, &rect));
    SDL_CHECK_SUCCESS(SDL_UpdateTexture(texture, &rect, surface_rgba->pixels, surface_rgba->pitch));
    SDL_CHECK_SUCCESS(SDL_RenderCopy(renderer, texture, &rect, &rect));
    SDL_RenderPresent(renderer);
}

//int counter= 0 ;
//char bmpName[256];
void _nextpage(void)
{
    Uint32 ticks;

    _handle_events();

    SDL_CHECK_SUCCESS( SDL_BlitSurface(surface, NULL, surface_rgba, NULL) );
    SDL_CHECK_SUCCESS( SDL_UpdateTexture(texture, NULL, surface_rgba->pixels, surface_rgba->pitch) );
    SDL_CHECK_SUCCESS( SDL_RenderCopy(renderer, texture, NULL, NULL) );
    SDL_RenderPresent(renderer);
    
    //sprintf(bmpName,"%d.bmp",counter++);
    //SDL_SaveBMP(surface,bmpName);
    
    //if (CLEAR_FRAMEBUFFER)
    //    SDL_FillRect(surface,NULL,0);

    ticks = getticks();
    total_render_time = (ticks - last_render_ticks);
    if (total_render_time > 1000){
        total_rendered_frames = 0;
        total_render_time = 1;
        last_render_ticks = ticks;
    } 
    total_rendered_frames++;
} 

uint8_t  readpixel(uint8_t* offset)
{
    return *offset;
} 

void drawpixel(uint8_t* location, uint8_t pixel)
{
    *location = pixel;
}

/* Fix this up The Right Way (TM) - DDOI */
void setcolor16(uint8_t col)
{
	drawpixel_color = col;
}

void drawpixel16(int32_t offset)
{
    drawpixel((uint8_t*)surface->pixels + offset, drawpixel_color);
} /* drawpixel16 */


void fillscreen16(int32_t offset, int32_t color, int32_t blocksize)
{
    Uint8 *surface_end;
    Uint8 *wanted_end;
    Uint8 *pixels;

	if (SDL_MUSTLOCK(surface))
        SDL_LockSurface(surface);

    pixels = get_framebuffer();

    /* Make this function pageoffset aware - DDOI */
    if (!pageoffset) { 
	    offset = offset << 3;
	    offset += 640*336;
    }

    surface_end = (pixels + (surface->w * surface->h)) - 1;
    wanted_end = (pixels + offset) + blocksize;

    if (offset < 0)
        offset = 0;

    if (wanted_end > surface_end)
        blocksize = ((uint32_t) surface_end) - ((uint32_t) pixels + offset);

    memset(pixels + offset, (int) color, blocksize);

    if (SDL_MUSTLOCK(surface))
        SDL_UnlockSurface(surface);

    _nextpage();
} /* fillscreen16 */


/* Most of this line code is taken from Abrash's "Graphics Programming Blackbook".
Remember, sharing code is A Good Thing. AH */
static inline void DrawHorizontalRun (uint8_t  **ScreenPtr, int XAdvance, int RunLength, uint8_t  Color)
{
    int i;
    uint8_t  *WorkingScreenPtr = *ScreenPtr;

    for (i=0; i<RunLength; i++)
    {
        *WorkingScreenPtr = Color;
        WorkingScreenPtr += XAdvance;
    }
    WorkingScreenPtr += surface->w;
    *ScreenPtr = WorkingScreenPtr;
}

static inline void DrawVerticalRun (uint8_t  **ScreenPtr, int XAdvance, int RunLength, uint8_t  Color)
{
    int i;
    uint8_t  *WorkingScreenPtr = *ScreenPtr;

    for (i=0; i<RunLength; i++)
    {
      	*WorkingScreenPtr = Color;
    	WorkingScreenPtr += surface->w;
    }
    WorkingScreenPtr += XAdvance;
    *ScreenPtr = WorkingScreenPtr;
}

void drawline16(int32_t XStart, int32_t YStart, int32_t XEnd, int32_t YEnd, uint8_t  Color)
{
    int Temp, AdjUp, AdjDown, ErrorTerm, XAdvance, XDelta, YDelta;
    int WholeStep, InitialPixelCount, FinalPixelCount, i, RunLength;
    uint8_t  *ScreenPtr;
    int32_t dx, dy;

    if (SDL_MUSTLOCK(surface))
        SDL_LockSurface(surface);

	dx = XEnd-XStart;
    dy = YEnd-YStart;
    
    //Analyse the slope
	if (dx >= 0)
	{
		if ((XStart > 639) || (XEnd < 0)) return;
		if (XStart < 0) { if (dy) YStart += scale(0-XStart,dy,dx); XStart = 0; }
		if (XEnd > 639) { if (dy) YEnd += scale(639-XEnd,dy,dx); XEnd = 639; }
	}
	else
	{
		if ((XEnd > 639) || (XStart < 0)) return;
		if (XEnd < 0) { if (dy) YEnd += scale(0-XEnd,dy,dx); XEnd = 0; }
		if (XStart > 639) { if (dy) YStart += scale(639-XStart,dy,dx); XStart = 639; }
	}
	if (dy >= 0)
	{
		if ((YStart >= ydim16) || (YEnd < 0)) return;
		if (YStart < 0) { if (dx) XStart += scale(0-YStart,dx,dy); YStart = 0; }
		if (YEnd >= ydim16) { if (dx) XEnd += scale(ydim16-1-YEnd,dx,dy); YEnd = ydim16-1; }
	}
	else
	{
		if ((YEnd >= ydim16) || (YStart < 0)) return;
		if (YEnd < 0) { if (dx) XEnd += scale(0-YEnd,dx,dy); YEnd = 0; }
		if (YStart >= ydim16) { if (dx) XStart += scale(ydim16-1-YStart,dx,dy); YStart = ydim16-1; }
	}

	/* Make sure the status bar border draws correctly - DDOI */
	if (!pageoffset) { YStart += 336; YEnd += 336; }

    /* We'll always draw top to bottom */
    if (YStart > YEnd) {
        Temp = YStart;
        YStart = YEnd;
        YEnd = Temp;
        Temp = XStart;
        XStart = XEnd;
        XEnd = Temp;
    }

    /* Point to the bitmap address first pixel to draw */
    ScreenPtr = (uint8_t  *) (get_framebuffer()) + XStart + (surface->w * YStart);

    /* Figure out whether we're going left or right, and how far we're going horizontally */
    if ((XDelta = XEnd - XStart) < 0)
    {
        XAdvance = (-1);
        XDelta = -XDelta;
    } else {
        XAdvance = 1;
    }

    /* Figure out how far we're going vertically */
    YDelta = YEnd - YStart;

    /* Special cases: Horizontal, vertical, and diagonal lines */
    if (XDelta == 0)
    {
        for (i=0; i <= YDelta; i++)
        {
            *ScreenPtr = Color;
            ScreenPtr += surface->w;
        }

        UNLOCK_SURFACE_AND_RETURN;
    }
    if (YDelta == 0)
    {
    	for (i=0; i <= XDelta; i++)
    	{
      	    *ScreenPtr = Color;
    	    ScreenPtr += XAdvance;
    	}
        UNLOCK_SURFACE_AND_RETURN;
    }
    if (XDelta == YDelta)
    {
    	for (i=0; i <= XDelta; i++)
        {
            *ScreenPtr = Color;
            ScreenPtr += XAdvance + surface->w;
        }
        UNLOCK_SURFACE_AND_RETURN;
    }

    /* Determine whether the line is X or Y major, and handle accordingly */
    if (XDelta >= YDelta) /* X major line */
    {
        WholeStep = XDelta / YDelta;
        AdjUp = (XDelta % YDelta) * 2;
        AdjDown = YDelta * 2;
        ErrorTerm = (XDelta % YDelta) - (YDelta * 2);

        InitialPixelCount = (WholeStep / 2) + 1;
        FinalPixelCount = InitialPixelCount;

        if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) InitialPixelCount--;
        if ((WholeStep & 0x01) != 0) ErrorTerm += YDelta;

        DrawHorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color);

        for (i=0; i<(YDelta-1); i++)
        {
            RunLength = WholeStep;
            if ((ErrorTerm += AdjUp) > 0)
            {
        	RunLength ++;
        	ErrorTerm -= AdjDown;
            }

            DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color);
         }

         DrawHorizontalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color);

         UNLOCK_SURFACE_AND_RETURN;
    } else {	/* Y major line */
    	WholeStep = YDelta / XDelta;
    	AdjUp = (YDelta % XDelta) * 2;
    	AdjDown = XDelta * 2;
        ErrorTerm = (YDelta % XDelta) - (XDelta * 2);
        InitialPixelCount = (WholeStep / 2) + 1;
        FinalPixelCount = InitialPixelCount;

        if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) InitialPixelCount --;
        if ((WholeStep & 0x01) != 0) ErrorTerm += XDelta;

        DrawVerticalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color);

        for (i=0; i<(XDelta-1); i++)
        {
            RunLength = WholeStep;
            if ((ErrorTerm += AdjUp) > 0)
            {
            	RunLength ++;
            	ErrorTerm -= AdjDown;
            }

            DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color);
        }

        DrawVerticalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color);
        UNLOCK_SURFACE_AND_RETURN;
     }
} /* drawline16 */


void clear2dscreen(void)
{
    SDL_Rect rect;

    rect.x = rect.y = 0;
    rect.w = surface->w;

	if (qsetmode == 350)
        rect.h = 350;
	else if (qsetmode == 480)
	{
		if (ydim16 <= 336)
            rect.h = 336;
        else
            rect.h = 480;
	} /* else if */

    SDL_FillRect(surface, &rect, 0);
} /* clear2dscreen */


void _idle(void)
{
    if (surface != NULL)
        _handle_events();
    SDL_Delay(1);
} /* _idle */

void *_getVideoBase(void)
{
    return((void *) surface->pixels);
}


//-------------------------------------------------------------------------------------------------
//  TIMER
//=================================================================================================




// FIX_00007: game speed corrected. The game speed is now as the real
// DOS duke3d. Unloading a full 200 bullet pistol must take 45.1 sec.
// SDL timer was not fast/accurate enough and was slowing down the gameplay,
// so bad


static int64_t timerfreq=0;
static int32_t timerlastsample=0;
static int timerticspersec=0;
static void (*usertimercallback)(void) = NULL;

//  This timer stuff is all Ken's idea.

//
// installusertimercallback() -- set up a callback function to be called when the timer is fired
//
void (*installusertimercallback(void (*callback)(void)))(void)
{
	void (*oldtimercallback)(void);

	oldtimercallback = usertimercallback;
	usertimercallback = callback;

	return oldtimercallback;
}


/*
 inittimer() -- initialise timer
 FCS: The tickspersecond parameter is a ratio value that helps replicating
      oldschool DOS tick per seconds.
 
      The way the timer work is:
      float newSystemTickPerSecond = [0,1]
      tickPerSecond on a DOS system = tickspersecond * newSystemTickPerSecond ;
*/

int inittimer(int tickspersecond)
{
	int64_t t;	
    
	if (timerfreq) return 0;	// already installed

	//printf("Initialising timer, with tickPerSecond=%d\n",tickspersecond);

	timerfreq = 1000;
	timerticspersec = tickspersecond;
    t = SDL_GetTicks();
	timerlastsample = (int32_t)(t*timerticspersec / timerfreq);

	usertimercallback = NULL;
    
	return 0;
}

//
// uninittimer() -- shut down timer
//
void uninittimer(void)
{
	if (!timerfreq) return;

	timerfreq=0;
	timerticspersec = 0;
}

//
// sampletimer() -- update totalclock
//
void sampletimer(void)
{
	int64_t i;
	int32_t n;
	
	if (!timerfreq) return;

    i = SDL_GetTicks();    
    
	n = (int32_t)(i*timerticspersec / timerfreq) - timerlastsample;
	if (n>0) {
		totalclock += n;
		timerlastsample += n;
	}

	if (usertimercallback) for (; n>0; n--) usertimercallback();
}

/*
   getticks() -- returns the windows ticks count
   FCS: This seeems to be only used in the multiplayer code
*/
uint32_t getticks(void)
{
	int64_t i;
    i = SDL_GetTicks();
	return (uint32_t)(i*(int32_t)(1000)/timerfreq);
}

//
// gettimerfreq() -- returns the number of ticks per second the timer is configured to generate
//
int gettimerfreq(void)
{
	return timerticspersec;
}