// Copyright (C) 1996, 1997 Keith Whitwell. // This file may only be copied under the terms of the GNU Library General // Public License - see the file COPYING in the lib3d distribution. // MSDOS device driver for djgpp v2 using the Allegro library v2.2. // This file is contributed by // Markus F.X.J. Oberhumer #if defined(__MSDOS__) && defined(__DJGPP__) && (__DJGPP__ >= 2) #undef NO_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(ALLEGRO_VERSION) || (ALLEGRO_VERSION < 3) # error Please upgrade to Allegro v3.0 or higher #endif /* we only need the following color drivers */ DECLARE_COLOR_DEPTH_LIST(COLOR_DEPTH_8 COLOR_DEPTH_16 COLOR_DEPTH_32); /*********************************************************************** // installing a timer (needed for mouse support) interferes // with uclock() - workaround ************************************************************************/ #define SIMULATE_UCLOCK #if defined(SIMULATE_UCLOCK) static volatile unsigned long my_timer_ticks = 0; // this will get called MY_TIMER_BPS times a second #define MY_TIMER_BPS 1000 static void my_timer_int(...) { my_timer_ticks++; } END_OF_FUNCTION(my_timer_int); #endif static bool timerInstalled = false; #undef uclock uclock_t djgpp_uclock(void) { if (!timerInstalled) return uclock(); #if defined(SIMULATE_UCLOCK) return uclock_t(my_timer_ticks * (double(UCLOCKS_PER_SEC) / MY_TIMER_BPS)); #else return uclock_t(clock() * (double(UCLOCKS_PER_SEC) / CLOCKS_PER_SEC)); #endif } /*********************************************************************** // ************************************************************************/ // Abstract base for the Allegro devices. class AllegroDevice : public Device { public: AllegroDevice( Exemplar ); AllegroDevice( uint width, uint height, uint depth, int pages ); ~AllegroDevice(); const char *getName() const { return "AllegroDevice"; } ostream & print( ostream &out ) const; uint allocateColour( uint red, uint green, uint blue ); void setSyncBehaviour(bool b) { syncOnSwaps = b; } void processPendingEvents(); void enableMouseCapability(); void enableKeyboardCapability(); void disableMouseCapability(); void disableKeyboardCapability(); protected: static bool findResolution(uint &x, uint &y, uint &depth, int pages=-1); static uchar *mapVideoMemory(unsigned phys_addr, unsigned phys_size); virtual bool setMode(const int *gfx_mode_list); protected: bool initialized; int pages; uint allegroDepth; uint physicalWidth, physicalHeight; uint virtualWidth, virtualHeight; uint windowLeft, windowTop; bool syncOnSwaps; int lastX, lastY; int colourIndex; int backgroundColour; // info unsigned lfbMappedAt; }; // // DOUBLE_BUFFER // Draws onto a memory bitmap and then uses a brute-force blit // to copy the entire image across to the screen. // class BlitAllegroDevice : public AllegroDevice { public: BlitAllegroDevice( Exemplar ); BlitAllegroDevice( uint width, uint height, uint depth ); ~BlitAllegroDevice(); const char *getName() const { return "BlitAllegroDevice"; } void swapBuffers(); protected: bool initialize(); protected: int estimateSpeed() const { return 11; } Device *clone( uint width, uint height, uint depth ); protected: BITMAP *bitmap; }; // // PAGE_FLIPPING (requires VESA 2.0 linear framebuffer) // Uses two pages of video memory, and flips back and forth between // them. It will only work if there is enough video memory to set up // dual pages. // class FlipAllegroDevice : public AllegroDevice { public: FlipAllegroDevice( Exemplar ); FlipAllegroDevice( uint width, uint height, uint depth ); ~FlipAllegroDevice(); const char *getName() const { return "FlipAllegroDevice"; } void swapBuffers(); protected: bool initialize(); protected: int estimateSpeed() const { return 9; } Device *clone( uint width, uint height, uint depth ); protected: uchar *frameBuf0, *frameBuf1, *frameBuf2; }; /*********************************************************************** // helper functions ************************************************************************/ // Find a suitable physical screen resolution bool AllegroDevice::findResolution(uint &x, uint &y, uint &depth, int pages) { if (depth <= 8) depth = 8; else if (depth <= 16) depth = 16; else if (depth <= 24) depth = 24; else if (depth <= 32) depth = 32; else return false; bool vesa = (pages > 1); // VBE 2.0 LFB needed ? bool modex = (pages == 1) && (depth == 8); // Mode-X allowed ? // be conservative and only allow resolutions that are // supported by all SVGA cards if (x <= 320 && y <= 200) x = 320, y = 200; else if (modex && x <= 320 && y <= 240) x = 320, y = 240; else if (modex && x <= 320 && y <= 400) x = 320, y = 400; else if (modex && x <= 360 && y <= 200) x = 360, y = 200; else if (modex && x <= 360 && y <= 240) x = 360, y = 240; else if (modex && x <= 360 && y <= 400) x = 360, y = 400; else if (vesa && x <= 640 && y <= 400) x = 640, y = 400; else if (x <= 640 && y <= 480) x = 640, y = 480; else if (x <= 800 && y <= 600) x = 800, y = 600; else if (x <= 1024 && y <= 768) x = 1024, y = 768; else if (vesa && x <= 1280 && y <= 1024) x = 1280, y = 1024; else return false; return true; } // Map the video memory into our address space so that it can // be accssed via frameBuf. uchar * AllegroDevice::mapVideoMemory(unsigned phys_addr, unsigned phys_size) { unsigned char *mem, *vidmem; unsigned vidsize; int delta; // Sanity check if (phys_addr < 0xa0000 || phys_size < 0x10000) return 0; if ((phys_addr & 0xfff) || (phys_size & 0xfff)) return 0; // Page align video memory size vidsize = (phys_size + 0xfff) & ~0xfff; // Allocate a buffer for video memory (using sbrk !) // Note that this memory is not wasted, as it is never touched. delta = vidsize + 0x1000; mem = (unsigned char *) sbrk(delta); if (mem == (unsigned char *)-1) return 0; // Page align video memory pointer vidmem = (unsigned char *) (((unsigned)mem + 0xfff) & ~0xfff); // Now map the video memory into our address space. // This requires DPMI 1.0 or CWSDPMI ! if (__djgpp_map_physical_memory(vidmem, vidsize, phys_addr) == 0) return vidmem; /* success */ #if 0 if (errno == EACCES) printf("mapping rejected !\n"); else printf("mapping failed !\n"); #endif // Free the buffer sbrk(-delta); // Our DPMI host doesn't support physical memory mapping. // We could try to enable nearptrs now, but this turns off all // memory protection and is a bad practice. return 0; } ostream & AllegroDevice::print( ostream &out ) const { const char *n = "[unknown]"; const char *d = NULL; if (gfx_driver && gfx_driver->name && gfx_driver->name[0]) n = gfx_driver->name; if (gfx_driver && gfx_driver->desc && gfx_driver->desc[0]) d = gfx_driver->desc; Debuggable::print(out); out << "\n\tdriver: "<< n << " " << physicalWidth << "x" << physicalHeight << "x" << depth; if (allegroDepth != depth) out << "[x" << allegroDepth << "]"; if (d) out << "\n\tdriver description: "<< d; if (gfx_driver && gfx_driver == &gfx_vesa_2l) { char s[256]; sprintf(s,"\n\tVESA 2.0 LFB at 0x%lx, size 0x%lx", (long) gfx_driver->vid_phys_base, (long) gfx_driver->vid_mem); out << s; if (lfbMappedAt) { sprintf(s," mapped at %x:%x", _my_ds(), lfbMappedAt); out << s; } } out << "\n\twidth:"<= 24) ? 32 : depth; set_color_depth(allegroDepth); pixelSize = (depth > 16) ? 4 : ((depth > 8) ? 2 : 1); windowLeft = (physicalWidth - width) / 2; windowLeft &= ~0x03; // align to 4 pixels windowTop = (physicalHeight - height) / 2; backgroundColour = (depth == 8) ? 0 : makecol(0,0,0); debug() << "0: " << *this << endl; debug() << "Using:" << " depth=" << depth << " allegroDepth=" << allegroDepth << " pixelSize=" << pixelSize << endlog; #if defined(SIMULATE_UCLOCK) LOCK_VARIABLE(my_timer_ticks); LOCK_FUNCTION(my_timer_int); #endif } bool AllegroDevice::setMode(const int *gfx_mode_list) { debug() << "2: " << *this << endl; int gfx_mode; bool found = false; while (!found && (gfx_mode = *gfx_mode_list++) >= 0) { if (set_gfx_mode(gfx_mode, physicalWidth, physicalHeight, 0, virtualHeight) == 0 && gfx_driver && screen) found = true; } if (!found) { cerr << getName() << ": Unable to set graphics mode " << physicalWidth << "x" << physicalHeight << "x" << depth << endl; return false; } // sanity check if (SCREEN_W < (int)width || SCREEN_H < (int)height || VIRTUAL_W < SCREEN_W || VIRTUAL_H < (int)virtualHeight) { cerr << getName() << ": Something weird happened !!!" << endl; return false; } physicalWidth = SCREEN_W; physicalHeight = SCREEN_H; virtualWidth = VIRTUAL_W; virtualHeight = VIRTUAL_H; #if defined(SIMULATE_UCLOCK) my_timer_ticks = 0; if (!timerInstalled) { install_timer(); if (install_int_ex(my_timer_int, BPS_TO_TIMER(MY_TIMER_BPS)) == 0) timerInstalled = true; } #else if (!timerInstalled) { install_timer(); timerInstalled = true; } #endif install_mouse(); lastX = mouse_x; lastY = mouse_y; return true; } AllegroDevice::~AllegroDevice() { if (isActive()) { allegro_exit(); timerInstalled = false; } } uint AllegroDevice::allocateColour( uint red, uint green, uint blue ) { if (depth == 8) { if (colourIndex < PAL_SIZE) { RGB rgb; rgb.r = (red >> 10) & 63; rgb.g = (green >> 10) & 63; rgb.b = (blue >> 10) & 63; set_color(colourIndex,&rgb); return colourIndex++; } if_debug { debug() << "Failed to allocate colour: " << endl << "\tSearching for closest available colour." << endlog; } } return makecol((red >> 8) & 255, (green >> 8) & 255, (blue >> 8) & 255); } /*********************************************************************** // mouse and keyboard ************************************************************************/ void AllegroDevice::processPendingEvents() { int mx = mouse_x; int my = mouse_y; mouseXRel += mx - lastX; mouseYRel += my - lastY; lastX = mx; lastY = my; } void AllegroDevice::enableMouseCapability() { } void AllegroDevice::disableMouseCapability() { } void AllegroDevice::enableKeyboardCapability() { } void AllegroDevice::disableKeyboardCapability() { } /*********************************************************************** // ************************************************************************/ static BlitAllegroDevice blitAdvertisement( Device::exemplar ); BlitAllegroDevice::BlitAllegroDevice( Exemplar e ) : AllegroDevice( e ) { registerChildClass( this ); } BlitAllegroDevice::BlitAllegroDevice( uint _width, uint _height, uint _min_depth ) : AllegroDevice( _width, _height, _min_depth, 1 ), bitmap(0) { if (isBad()) return; // Abort if the parent constructor failed. rowSize = width * pixelSize; bitmap = create_bitmap(width,height); frameBuf = bitmap ? (uchar *) bitmap->dat : 0; if (bitmap == 0 || frameBuf == 0) { cerr << getName() << ": Unable to allocate bitmap." << endl; setBad(); return; } clear_to_color(bitmap,backgroundColour); debug() << "1: " << *this << endl; } bool BlitAllegroDevice::initialize() { if (initialized) return initialized; static const int modes[] = { GFX_VESA2L, GFX_VESA2B, GFX_VGA, GFX_AUTODETECT, -1 }; if (!setMode(modes)) { setBad(); return false; } rectfill(screen,0,0,physicalWidth,physicalHeight,backgroundColour); initialized = true; return initialized; } BlitAllegroDevice::~BlitAllegroDevice() { if (bitmap) { destroy_bitmap(bitmap); bitmap = 0; } } Device * BlitAllegroDevice::clone( uint width, uint height, uint depth ) { return new BlitAllegroDevice( width, height, depth ); } void BlitAllegroDevice::swapBuffers() { uint xn = min(xmin, oldxmin); uint xx = max(xmax, oldxmax); uint yn = min(ymin, oldymin); uint yx = max(ymax, oldymax); if (syncOnSwaps) vsync(); if (xn < xx && yn < yx) { xn &= ~0x03; if (xx & 0x03) { xx &= ~0x03; xx += 0x04; } // copy the bitmap onto the screen blit(bitmap, screen, xn, yn, windowLeft+xn, windowTop+yn, xx-xn, yx-yn); } } /*********************************************************************** // ************************************************************************/ static FlipAllegroDevice flipAdvertisement( Device::exemplar ); FlipAllegroDevice::FlipAllegroDevice( Exemplar e ) : AllegroDevice(e) { registerChildClass( this ); } FlipAllegroDevice::FlipAllegroDevice( uint _width, uint _height, uint _min_depth ) : AllegroDevice( _width, _height, _min_depth, 2 ), frameBuf0(0), frameBuf1(0), frameBuf2(0) { if (isBad()) return; // Abort if the parent constructor failed. if (windows_version == 3) { // don't even try this under Windows 3.x setBad(); return; } virtualHeight = physicalHeight * 2; // minimum virtual height debug() << "1: " << *this << endl; } bool FlipAllegroDevice::initialize() { if (initialized) return initialized; static const int modes[] = { GFX_VESA2L, -1 }; if (!setMode(modes) || !gfx_driver->linear || !is_linear_bitmap(screen)) { cerr << getName() << ": Unable to set a linear framebuffer graphics mode." << endl; setBad(); return false; } rowSize = virtualWidth * pixelSize; debug() << "3: " << *this << endl; frameBuf0 = mapVideoMemory(gfx_driver->vid_phys_base,gfx_driver->vid_mem); if (frameBuf0 == 0) { cerr << getName() << ": Unable to map video memory." << endl; setBad(); return false; } lfbMappedAt = (unsigned) frameBuf0; // center the screen frameBuf1 = frameBuf0; frameBuf1 += pixelSize * windowLeft + rowSize * windowTop; frameBuf2 = frameBuf1; frameBuf2 += rowSize * physicalHeight; frameBuf = frameBuf2; rectfill(screen,0,0,physicalWidth,2*physicalHeight,backgroundColour); initialized = true; return initialized; } FlipAllegroDevice::~FlipAllegroDevice() { } void FlipAllegroDevice::swapBuffers() { uint xn = min(xmin, oldxmin); uint xx = max(xmax, oldxmax); uint yn = min(ymin, oldymin); uint yx = max(ymax, oldymax); if (xn < xx && yn < yx) { // Just change the visual screen part and swap frameBuf. // Note that scroll_screen() implies a vsync(). if (frameBuf == frameBuf1) { scroll_screen(0,0); frameBuf = frameBuf2; } else if (frameBuf == frameBuf2) { scroll_screen(0,physicalHeight); frameBuf = frameBuf1; } } else { if (syncOnSwaps) vsync(); } } Device * FlipAllegroDevice::clone( uint width, uint height, uint depth ) { return new FlipAllegroDevice( width, height, depth ); } #endif /* defined(__MSDOS__) && defined(__DJGPP__) && (__DJGPP__ >= 2) */