--- BasiliskII/src/SDL/video_sdl.cpp 2004/07/02 06:08:01 1.11 +++ BasiliskII/src/SDL/video_sdl.cpp 2011/12/29 07:38:34 1.43 @@ -1,7 +1,7 @@ /* * video_sdl.cpp - Video/graphics emulation, SDL specific stuff * - * Basilisk II (C) 1997-2004 Christian Bauer + * Basilisk II (C) 1997-2008 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,22 +21,23 @@ /* * NOTES: * The Ctrl key works like a qualifier for special actions: - * Ctrl-Tab = suspend DGA mode + * Ctrl-Tab = suspend DGA mode (TODO) * Ctrl-Esc = emergency quit * Ctrl-F1 = mount floppy * Ctrl-F5 = grab mouse (in windowed mode) * * FIXMEs and TODOs: + * - Windows requires an extra mouse event to update the actual cursor image? + * - Ctr-Tab for suspend/resume but how? SDL does not support that for non-Linux * - Ctrl-Fn doesn't generate SDL_KEYDOWN events (SDL bug?) * - Mouse acceleration, there is no API in SDL yet for that * - Force relative mode in Grab mode even if SDL provides absolute coordinates? - * - Fullscreen mode * - Gamma tables support is likely to be broken here * - Events processing is bound to the general emulation thread as SDL requires * to PumpEvents() within the same thread as the one that called SetVideoMode(). * Besides, there can't seem to be a way to call SetVideoMode() from a child thread. - * - Refresh performance is still slow. Use SDL_CreateRGBSurface()? * - Backport hw cursor acceleration to Basilisk II? + * - Factor out code */ #include "sysdeps.h" @@ -47,6 +48,10 @@ #include #include +#ifdef WIN32 +#include /* alloca() */ +#endif + #include "cpu_emulation.h" #include "main.h" #include "adb.h" @@ -56,11 +61,15 @@ #include "video.h" #include "video_defs.h" #include "video_blit.h" +#include "vm_alloc.h" + +#if (defined(__APPLE__) && defined(__MACH__)) +#include "utils_macosx.h" +#endif #define DEBUG 0 #include "debug.h" - // Supported video modes using std::vector; static vector VideoModes; @@ -81,7 +90,11 @@ static int display_type = DISPLAY_WINDOW #endif // Constants +#ifdef WIN32 +const char KEYCODE_FILE_NAME[] = "BasiliskII_keycodes"; +#else const char KEYCODE_FILE_NAME[] = DATADIR "/keycodes"; +#endif // Global variables @@ -94,8 +107,14 @@ static uint8 *the_buffer_copy = NULL; static uint32 the_buffer_size; // Size of allocated the_buffer static bool redraw_thread_active = false; // Flag: Redraw thread installed +#ifndef USE_CPU_EMUL_SERVICES static volatile bool redraw_thread_cancel; // Flag: Cancel Redraw thread static SDL_Thread *redraw_thread = NULL; // Redraw thread +#ifdef SHEEPSHAVER +static volatile bool thread_stop_req = false; +static volatile bool thread_stop_ack = false; // Acknowledge for thread_stop_req +#endif +#endif #ifdef ENABLE_VOSF static bool use_vosf = false; // Flag: VOSF enabled @@ -120,7 +139,12 @@ static SDL_Cursor *sdl_cursor; // C static volatile bool cursor_changed = false; // Flag: cursor changed, redraw_func must update the cursor static SDL_Color sdl_palette[256]; // Color palette to be used as CLUT and gamma table static bool sdl_palette_changed = false; // Flag: Palette changed, redraw thread must set new colors -static const int sdl_eventmask = SDL_MOUSEBUTTONDOWNMASK | SDL_MOUSEBUTTONUPMASK | SDL_MOUSEMOTIONMASK | SDL_KEYUPMASK | SDL_KEYDOWNMASK | SDL_VIDEOEXPOSEMASK | SDL_QUITMASK; +static const int sdl_eventmask = SDL_MOUSEEVENTMASK | SDL_KEYEVENTMASK | SDL_VIDEOEXPOSEMASK | SDL_QUITMASK | SDL_ACTIVEEVENTMASK; + +// Mutex to protect SDL events +static SDL_mutex *sdl_events_lock = NULL; +#define LOCK_EVENTS SDL_LockMutex(sdl_events_lock) +#define UNLOCK_EVENTS SDL_UnlockMutex(sdl_events_lock) // Mutex to protect palette static SDL_mutex *sdl_palette_lock = NULL; @@ -145,6 +169,97 @@ extern void SysMountFirstFloppy(void); /* + * SDL surface locking glue + */ + +#ifdef ENABLE_VOSF +#define SDL_VIDEO_LOCK_VOSF_SURFACE(SURFACE) do { \ + if ((SURFACE)->flags & (SDL_HWSURFACE | SDL_FULLSCREEN)) \ + the_host_buffer = (uint8 *)(SURFACE)->pixels; \ +} while (0) +#else +#define SDL_VIDEO_LOCK_VOSF_SURFACE(SURFACE) +#endif + +#define SDL_VIDEO_LOCK_SURFACE(SURFACE) do { \ + if (SDL_MUSTLOCK(SURFACE)) { \ + SDL_LockSurface(SURFACE); \ + SDL_VIDEO_LOCK_VOSF_SURFACE(SURFACE); \ + } \ +} while (0) + +#define SDL_VIDEO_UNLOCK_SURFACE(SURFACE) do { \ + if (SDL_MUSTLOCK(SURFACE)) \ + SDL_UnlockSurface(SURFACE); \ +} while (0) + + +/* + * Framebuffer allocation routines + */ + +static void *vm_acquire_framebuffer(uint32 size) +{ + // always try to reallocate framebuffer at the same address + static void *fb = VM_MAP_FAILED; + if (fb != VM_MAP_FAILED) { + if (vm_acquire_fixed(fb, size) < 0) { +#ifndef SHEEPSHAVER + printf("FATAL: Could not reallocate framebuffer at previous address\n"); +#endif + fb = VM_MAP_FAILED; + } + } + if (fb == VM_MAP_FAILED) + fb = vm_acquire(size, VM_MAP_DEFAULT | VM_MAP_32BIT); + return fb; +} + +static inline void vm_release_framebuffer(void *fb, uint32 size) +{ + vm_release(fb, size); +} + + +/* + * Windows message handler + */ + +#ifdef WIN32 +#include +static WNDPROC sdl_window_proc = NULL; // Window proc used by SDL + +extern void SysMediaArrived(void); +extern void SysMediaRemoved(void); +extern HWND GetMainWindowHandle(void); + +static LRESULT CALLBACK windows_message_handler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_DEVICECHANGE: + if (wParam == DBT_DEVICEREMOVECOMPLETE) { + DEV_BROADCAST_HDR *p = (DEV_BROADCAST_HDR *)lParam; + if (p->dbch_devicetype == DBT_DEVTYP_VOLUME) + SysMediaRemoved(); + } + else if (wParam == DBT_DEVICEARRIVAL) { + DEV_BROADCAST_HDR *p = (DEV_BROADCAST_HDR *)lParam; + if (p->dbch_devicetype == DBT_DEVTYP_VOLUME) + SysMediaArrived(); + } + return 0; + + default: + if (sdl_window_proc) + return CallWindowProc(sdl_window_proc, hwnd, msg, wParam, lParam); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} +#endif + + +/* * SheepShaver glue */ @@ -189,69 +304,21 @@ static vector VideoMonit // Find Apple mode matching best specified dimensions static int find_apple_resolution(int xsize, int ysize) { - int apple_id; - if (xsize < 800) - apple_id = APPLE_640x480; - else if (xsize < 1024) - apple_id = APPLE_800x600; - else if (xsize < 1152) - apple_id = APPLE_1024x768; - else if (xsize < 1280) { - if (ysize < 900) - apple_id = APPLE_1152x768; - else - apple_id = APPLE_1152x900; - } - else if (xsize < 1600) - apple_id = APPLE_1280x1024; - else - apple_id = APPLE_1600x1200; - return apple_id; -} - -// Set parameters to specified Apple mode -static void set_apple_resolution(int apple_id, int &xsize, int &ysize) -{ - switch (apple_id) { - case APPLE_640x480: - xsize = 640; - ysize = 480; - break; - case APPLE_800x600: - xsize = 800; - ysize = 600; - break; - case APPLE_1024x768: - xsize = 1024; - ysize = 768; - break; - case APPLE_1152x768: - xsize = 1152; - ysize = 768; - break; - case APPLE_1152x900: - xsize = 1152; - ysize = 900; - break; - case APPLE_1280x1024: - xsize = 1280; - ysize = 1024; - break; - case APPLE_1600x1200: - xsize = 1600; - ysize = 1200; - break; - default: - abort(); - } -} - -// Match Apple mode matching best specified dimensions -static int match_apple_resolution(int &xsize, int &ysize) -{ - int apple_id = find_apple_resolution(xsize, ysize); - set_apple_resolution(apple_id, xsize, ysize); - return apple_id; + if (xsize == 640 && ysize == 480) + return APPLE_640x480; + if (xsize == 800 && ysize == 600) + return APPLE_800x600; + if (xsize == 1024 && ysize == 768) + return APPLE_1024x768; + if (xsize == 1152 && ysize == 768) + return APPLE_1152x768; + if (xsize == 1152 && ysize == 900) + return APPLE_1152x900; + if (xsize == 1280 && ysize == 1024) + return APPLE_1280x1024; + if (xsize == 1600 && ysize == 1200) + return APPLE_1600x1200; + return APPLE_CUSTOM; } // Display error alert @@ -324,7 +391,7 @@ static inline int bytes_per_pixel(int de } // Map video_mode depth ID to numerical depth value -static int sdl_depth_of_video_depth(int video_depth) +static int mac_depth_of_video_depth(int video_depth) { int depth = -1; switch (video_depth) { @@ -352,60 +419,80 @@ static int sdl_depth_of_video_depth(int return depth; } -// Check wether specified mode is available -static bool has_mode(int type, int width, int height) +// Map video_mode depth ID to SDL screen depth +static int sdl_depth_of_video_depth(int video_depth) { - // FIXME: no fullscreen support yet - if (type == DISPLAY_SCREEN) - return false; + return (video_depth <= VIDEO_DEPTH_8BIT) ? 8 : mac_depth_of_video_depth(video_depth); +} + +// Get screen dimensions +static void sdl_display_dimensions(int &width, int &height) +{ + static int max_width, max_height; + if (max_width == 0 && max_height == 0) { + max_width = 640 ; max_height = 480; + SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); + if (modes && modes != (SDL_Rect **)-1) { + // It turns out that on some implementations, and contrary to the documentation, + // the returned list is not sorted from largest to smallest (e.g. Windows) + for (int i = 0; modes[i] != NULL; i++) { + const int w = modes[i]->w; + const int h = modes[i]->h; + if (w > max_width && h > max_height) { + max_width = w; + max_height = h; + } + } + } + } + width = max_width; + height = max_height; +} +static inline int sdl_display_width(void) +{ + int width, height; + sdl_display_dimensions(width, height); + return width; +} + +static inline int sdl_display_height(void) +{ + int width, height; + sdl_display_dimensions(width, height); + return height; +} + +// Check wether specified mode is available +static bool has_mode(int type, int width, int height, int depth) +{ #ifdef SHEEPSHAVER - // Filter out Classic resolutiosn + // Filter out Classic resolutions if (width == 512 && height == 384) return false; - - // Read window modes prefs - static uint32 window_modes = 0; - static uint32 screen_modes = 0; - if (window_modes == 0 || screen_modes == 0) { - window_modes = PrefsFindInt32("windowmodes"); - screen_modes = PrefsFindInt32("screenmodes"); - if (window_modes == 0 || screen_modes == 0) - window_modes |= 3; // Allow at least 640x480 and 800x600 window modes - } - - if (type == DISPLAY_WINDOW) { - int apple_mask, apple_id = find_apple_resolution(width, height); - switch (apple_id) { - case APPLE_640x480: apple_mask = 0x01; break; - case APPLE_800x600: apple_mask = 0x02; break; - case APPLE_1024x768: apple_mask = 0x04; break; - case APPLE_1152x768: apple_mask = 0x40; break; - case APPLE_1152x900: apple_mask = 0x08; break; - case APPLE_1280x1024: apple_mask = 0x10; break; - case APPLE_1600x1200: apple_mask = 0x20; break; - default: apple_mask = 0x00; break; - } - return (window_modes & apple_mask); - } -#else - return true; #endif - return false; + + // Filter out out-of-bounds resolutions + if (width > sdl_display_width() || height > sdl_display_height()) + return false; + + // Rely on SDL capabilities + return SDL_VideoModeOK(width, height, + sdl_depth_of_video_depth(depth), + SDL_HWSURFACE | (type == DISPLAY_SCREEN ? SDL_FULLSCREEN : 0)); } // Add mode to list of supported modes static void add_mode(int type, int width, int height, int resolution_id, int bytes_per_row, int depth) { // Filter out unsupported modes - if (!has_mode(type, width, height)) + if (!has_mode(type, width, height, depth)) return; // Fill in VideoMode entry VIDEO_MODE mode; #ifdef SHEEPSHAVER - // Recalculate dimensions to fit Apple modes - resolution_id = match_apple_resolution(width, height); + resolution_id = find_apple_resolution(width, height); mode.viType = type; #endif VIDEO_MODE_X = width; @@ -416,19 +503,6 @@ static void add_mode(int type, int width VideoModes.push_back(mode); } -// Add standard list of windowed modes for given color depth -static void add_window_modes(int depth) -{ - video_depth vdepth = (video_depth)depth; - add_mode(DISPLAY_WINDOW, 512, 384, 0x80, TrivialBytesPerRow(512, vdepth), depth); - add_mode(DISPLAY_WINDOW, 640, 480, 0x81, TrivialBytesPerRow(640, vdepth), depth); - add_mode(DISPLAY_WINDOW, 800, 600, 0x82, TrivialBytesPerRow(800, vdepth), depth); - add_mode(DISPLAY_WINDOW, 1024, 768, 0x83, TrivialBytesPerRow(1024, vdepth), depth); - add_mode(DISPLAY_WINDOW, 1152, 870, 0x84, TrivialBytesPerRow(1152, vdepth), depth); - add_mode(DISPLAY_WINDOW, 1280, 1024, 0x85, TrivialBytesPerRow(1280, vdepth), depth); - add_mode(DISPLAY_WINDOW, 1600, 1200, 0x86, TrivialBytesPerRow(1600, vdepth), depth); -} - // Set Mac frame layout and base address (uses the_buffer/MacFrameBaseMac) static void set_mac_frame_buffer(SDL_monitor_desc &monitor, int depth, bool native_byte_order) { @@ -468,10 +542,59 @@ static void set_window_name(int name) // Set mouse grab mode static SDL_GrabMode set_grab_mode(SDL_GrabMode mode) { - const SDL_VideoInfo *vi =SDL_GetVideoInfo(); + const SDL_VideoInfo *vi = SDL_GetVideoInfo(); return (vi && vi->wm_available ? SDL_WM_GrabInput(mode) : SDL_GRAB_OFF); } +// Migrate preferences items (XXX to be handled in MigratePrefs()) +static void migrate_screen_prefs(void) +{ +#ifdef SHEEPSHAVER + // Look-up priorities are: "screen", "screenmodes", "windowmodes". + if (PrefsFindString("screen")) + return; + + uint32 window_modes = PrefsFindInt32("windowmodes"); + uint32 screen_modes = PrefsFindInt32("screenmodes"); + int width = 0, height = 0; + if (screen_modes) { + static const struct { + int id; + int width; + int height; + } + modes[] = { + { 1, 640, 480 }, + { 2, 800, 600 }, + { 4, 1024, 768 }, + { 64, 1152, 768 }, + { 8, 1152, 900 }, + { 16, 1280, 1024 }, + { 32, 1600, 1200 }, + { 0, } + }; + for (int i = 0; modes[i].id != 0; i++) { + if (screen_modes & modes[i].id) { + if (width < modes[i].width && height < modes[i].height) { + width = modes[i].width; + height = modes[i].height; + } + } + } + } else { + if (window_modes & 1) + width = 640, height = 480; + if (window_modes & 2) + width = 800, height = 600; + } + if (width && height) { + char str[32]; + sprintf(str, "%s/%d/%d", screen_modes ? "dga" : "win", width, height); + PrefsReplaceString("screen", str); + } +#endif +} + /* * Display "driver" classes @@ -505,12 +628,12 @@ public: class driver_window; static void update_display_window_vosf(driver_window *drv); static void update_display_dynamic(int ticker, driver_window *drv); -static void update_display_static(driver_window *drv); +static void update_display_static(driver_base *drv); class driver_window : public driver_base { friend void update_display_window_vosf(driver_window *drv); friend void update_display_dynamic(int ticker, driver_window *drv); - friend void update_display_static(driver_window *drv); + friend void update_display_static(driver_base *drv); public: driver_window(SDL_monitor_desc &monitor); @@ -527,6 +650,12 @@ private: int mouse_last_x, mouse_last_y; // Last mouse position (for relative mode) }; +class driver_fullscreen : public driver_base { +public: + driver_fullscreen(SDL_monitor_desc &monitor); + ~driver_fullscreen(); +}; + static driver_base *drv = NULL; // Pointer to currently used driver object #ifdef ENABLE_VOSF @@ -548,12 +677,15 @@ driver_base::~driver_base() if (s) SDL_FreeSurface(s); + // the_buffer shall always be mapped through vm_acquire_framebuffer() + if (the_buffer != VM_MAP_FAILED) { + D(bug(" releasing the_buffer at %p (%d bytes)\n", the_buffer, the_buffer_size)); + vm_release_framebuffer(the_buffer, the_buffer_size); + the_buffer = NULL; + } + // Free frame buffer(s) if (!use_vosf) { - if (the_buffer) { - free(the_buffer); - the_buffer = NULL; - } if (the_buffer_copy) { free(the_buffer_copy); the_buffer_copy = NULL; @@ -561,12 +693,6 @@ driver_base::~driver_base() } #ifdef ENABLE_VOSF else { - // the_buffer shall always be mapped through vm_acquire() so that we can vm_protect() it at will - if (the_buffer != VM_MAP_FAILED) { - D(bug(" releasing the_buffer at %p (%d bytes)\n", the_buffer, the_buffer_size)); - vm_release(the_buffer, the_buffer_size); - the_buffer = NULL; - } if (the_host_buffer) { D(bug(" freeing the_host_buffer at %p\n", the_host_buffer)); free(the_host_buffer); @@ -577,6 +703,9 @@ driver_base::~driver_base() free(the_buffer_copy); the_buffer_copy = NULL; } + + // Deinitialize VOSF + video_vosf_exit(); } #endif } @@ -605,6 +734,8 @@ void driver_base::restore_mouse_accel(vo * Windowed display driver */ +static bool SDL_display_opened = false; + // Open display driver_window::driver_window(SDL_monitor_desc &m) : driver_base(m), mouse_grabbed(false) @@ -616,17 +747,29 @@ driver_window::driver_window(SDL_monitor // Set absolute mouse mode ADBSetRelMouseMode(mouse_grabbed); + // This is ugly: + // If we're switching resolutions (ie, not setting it for the first time), + // there's a bug in SDL where the SDL_Surface created will not be properly + // setup. The solution is to SDL_QuitSubSystem(SDL_INIT_VIDEO) before calling + // SDL_SetVideoMode for the second time (SDL_SetVideoMode will call SDL_Init() + // and all will be well). Without this, the video becomes corrupted (at least + // on Mac OS X), after the resolution switch. + if (SDL_display_opened) + SDL_QuitSubSystem(SDL_INIT_VIDEO); + // Create surface - int depth = ((int)VIDEO_MODE_DEPTH <= VIDEO_DEPTH_8BIT ? 8 : screen_depth); + int depth = sdl_depth_of_video_depth(VIDEO_MODE_DEPTH); if ((s = SDL_SetVideoMode(width, height, depth, SDL_HWSURFACE)) == NULL) return; + SDL_display_opened = true; + #ifdef ENABLE_VOSF use_vosf = true; // Allocate memory for frame buffer (SIZE is extended to page-boundary) the_host_buffer = (uint8 *)s->pixels; the_buffer_size = page_extend((aligned_height + 2) * s->pitch); - the_buffer = (uint8 *)vm_acquire(the_buffer_size); + the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size); the_buffer_copy = (uint8 *)malloc(the_buffer_size); D(bug("the_buffer = %p, the_buffer_copy = %p, the_host_buffer = %p\n", the_buffer, the_buffer_copy, the_host_buffer)); @@ -650,7 +793,7 @@ driver_window::driver_window(SDL_monitor // Allocate memory for frame buffer the_buffer_size = (aligned_height + 2) * s->pitch; the_buffer_copy = (uint8 *)calloc(1, the_buffer_size); - the_buffer = (uint8 *)calloc(1, the_buffer_size); + the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size); D(bug("the_buffer = %p, the_buffer_copy = %p\n", the_buffer, the_buffer_copy)); } @@ -675,7 +818,7 @@ driver_window::driver_window(SDL_monitor visualFormat.Rmask = f->Rmask; visualFormat.Gmask = f->Gmask; visualFormat.Bmask = f->Bmask; - Screen_blitter_init(visualFormat, true, sdl_depth_of_video_depth(VIDEO_MODE_DEPTH)); + Screen_blitter_init(visualFormat, true, mac_depth_of_video_depth(VIDEO_MODE_DEPTH)); // Load gray ramp to 8->16/32 expand map if (!IsDirectMode(mode)) @@ -742,6 +885,99 @@ void driver_window::mouse_moved(int x, i ADBMouseMoved(x, y); } + +/* + * Full-screen display driver + */ + +// Open display +driver_fullscreen::driver_fullscreen(SDL_monitor_desc &m) + : driver_base(m) +{ + int width = VIDEO_MODE_X, height = VIDEO_MODE_Y; + int aligned_width = (width + 15) & ~15; + int aligned_height = (height + 15) & ~15; + + // Set absolute mouse mode + ADBSetRelMouseMode(false); + + // Create surface + int depth = sdl_depth_of_video_depth(VIDEO_MODE_DEPTH); + if ((s = SDL_SetVideoMode(width, height, depth, SDL_HWSURFACE | SDL_FULLSCREEN)) == NULL) + return; + +#ifdef ENABLE_VOSF + use_vosf = true; + // Allocate memory for frame buffer (SIZE is extended to page-boundary) + the_host_buffer = (uint8 *)s->pixels; + the_buffer_size = page_extend((aligned_height + 2) * s->pitch); + the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size); + the_buffer_copy = (uint8 *)malloc(the_buffer_size); + D(bug("the_buffer = %p, the_buffer_copy = %p, the_host_buffer = %p\n", the_buffer, the_buffer_copy, the_host_buffer)); + + // Check whether we can initialize the VOSF subsystem and it's profitable + if (!video_vosf_init(m)) { + WarningAlert(STR_VOSF_INIT_ERR); + use_vosf = false; + } + else if (!video_vosf_profitable()) { + video_vosf_exit(); + printf("VOSF acceleration is not profitable on this platform, disabling it\n"); + use_vosf = false; + } + if (!use_vosf) { + free(the_buffer_copy); + vm_release(the_buffer, the_buffer_size); + the_host_buffer = NULL; + } +#endif + if (!use_vosf) { + // Allocate memory for frame buffer + the_buffer_size = (aligned_height + 2) * s->pitch; + the_buffer_copy = (uint8 *)calloc(1, the_buffer_size); + the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size); + D(bug("the_buffer = %p, the_buffer_copy = %p\n", the_buffer, the_buffer_copy)); + } + + // Hide cursor + SDL_ShowCursor(0); + + // Init blitting routines + SDL_PixelFormat *f = s->format; + VisualFormat visualFormat; + visualFormat.depth = depth; + visualFormat.Rmask = f->Rmask; + visualFormat.Gmask = f->Gmask; + visualFormat.Bmask = f->Bmask; + Screen_blitter_init(visualFormat, true, mac_depth_of_video_depth(VIDEO_MODE_DEPTH)); + + // Load gray ramp to 8->16/32 expand map + if (!IsDirectMode(mode)) + for (int i=0; i<256; i++) + ExpandMap[i] = SDL_MapRGB(f, i, i, i); + + // Set frame buffer base + set_mac_frame_buffer(monitor, VIDEO_MODE_DEPTH, true); + + // Everything went well + init_ok = true; +} + +// Close display +driver_fullscreen::~driver_fullscreen() +{ +#ifdef ENABLE_VOSF + if (use_vosf) + the_host_buffer = NULL; // don't free() in driver_base dtor +#endif + if (s) + SDL_FreeSurface(s); + + // Show cursor + SDL_ShowCursor(1); +} + + /* * Initialization */ @@ -773,6 +1009,7 @@ static void keycode_init(void) SDL_VideoDriverName(video_driver, sizeof(video_driver)); bool video_driver_found = false; char line[256]; + int n_keys = 0; while (fgets(line, sizeof(line) - 1, f)) { // Read line int len = strlen(line); @@ -785,15 +1022,16 @@ static void keycode_init(void) continue; if (video_driver_found) { - // Skip aliases + // Skip aliases as long as we have read keycodes yet + // Otherwise, it's another mapping and we have to stop static const char sdl_str[] = "sdl"; - if (strncmp(line, sdl_str, sizeof(sdl_str) - 1) == 0) + if (strncmp(line, sdl_str, sizeof(sdl_str) - 1) == 0 && n_keys == 0) continue; // Read keycode int x_code, mac_code; if (sscanf(line, "%d %d", &x_code, &mac_code) == 2) - keycode_table[x_code & 0xff] = mac_code; + keycode_table[x_code & 0xff] = mac_code, n_keys++; else break; } else { @@ -818,6 +1056,8 @@ static void keycode_init(void) WarningAlert(str); return; } + + D(bug("Using SDL/%s keycodes table, %d key mappings\n", video_driver, n_keys)); } } @@ -836,6 +1076,9 @@ bool SDL_monitor_desc::video_open(void) case DISPLAY_WINDOW: drv = new(std::nothrow) driver_window(*this); break; + case DISPLAY_SCREEN: + drv = new(std::nothrow) driver_fullscreen(*this); + break; } if (drv == NULL) return false; @@ -845,6 +1088,13 @@ bool SDL_monitor_desc::video_open(void) return false; } +#ifdef WIN32 + // Chain in a new message handler for WM_DEVICECHANGE + HWND the_window = GetMainWindowHandle(); + sdl_window_proc = (WNDPROC)GetWindowLongPtr(the_window, GWLP_WNDPROC); + SetWindowLongPtr(the_window, GWLP_WNDPROC, (LONG_PTR)windows_message_handler); +#endif + // Initialize VideoRefresh function VideoRefreshInit(); @@ -852,12 +1102,16 @@ bool SDL_monitor_desc::video_open(void) LOCK_FRAME_BUFFER; // Start redraw/input thread +#ifndef USE_CPU_EMUL_SERVICES redraw_thread_cancel = false; redraw_thread_active = ((redraw_thread = SDL_CreateThread(redraw_func, NULL)) != NULL); if (!redraw_thread_active) { printf("FATAL: cannot create redraw thread\n"); return false; } +#else + redraw_thread_active = true; +#endif return true; } @@ -878,6 +1132,8 @@ bool VideoInit(bool classic) #endif // Create Mutexes + if ((sdl_events_lock = SDL_CreateMutex()) == NULL) + return false; if ((sdl_palette_lock = SDL_CreateMutex()) == NULL) return false; if ((frame_buffer_lock = SDL_CreateMutex()) == NULL) @@ -892,13 +1148,12 @@ bool VideoInit(bool classic) mouse_wheel_lines = PrefsFindInt32("mousewheellines"); // Get screen mode from preferences + migrate_screen_prefs(); const char *mode_str = NULL; -#ifndef SHEEPSHAVER if (classic_mode) mode_str = "win/512/342"; else mode_str = PrefsFindString("screen"); -#endif // Determine display type and default dimensions int default_width, default_height; @@ -914,21 +1169,17 @@ bool VideoInit(bool classic) if (mode_str) { if (sscanf(mode_str, "win/%d/%d", &default_width, &default_height) == 2) display_type = DISPLAY_WINDOW; - } - int max_width = 640, max_height = 480; - SDL_Rect **modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); - if (modes && modes != (SDL_Rect **)-1) { - max_width = modes[0]->w; - max_height = modes[0]->h; - if (default_width > max_width) - default_width = max_width; - if (default_height > max_height) - default_height = max_height; + else if (sscanf(mode_str, "dga/%d/%d", &default_width, &default_height) == 2) + display_type = DISPLAY_SCREEN; } if (default_width <= 0) - default_width = max_width; + default_width = sdl_display_width(); + else if (default_width > sdl_display_width()) + default_width = sdl_display_width(); if (default_height <= 0) - default_height = max_height; + default_height = sdl_display_height(); + else if (default_height > sdl_display_height()) + default_height = sdl_display_height(); // Mac screen depth follows X depth screen_depth = SDL_GetVideoInfo()->vfmt->BitsPerPixel; @@ -948,19 +1199,53 @@ bool VideoInit(bool classic) break; } + // Initialize list of video modes to try + struct { + int w; + int h; + int resolution_id; + } + video_modes[] = { + { -1, -1, 0x80 }, + { 512, 384, 0x80 }, + { 640, 480, 0x81 }, + { 800, 600, 0x82 }, + { 1024, 768, 0x83 }, + { 1152, 870, 0x84 }, + { 1280, 1024, 0x85 }, + { 1600, 1200, 0x86 }, + { 0, } + }; + video_modes[0].w = default_width; + video_modes[0].h = default_height; + // Construct list of supported modes if (display_type == DISPLAY_WINDOW) { if (classic) add_mode(display_type, 512, 342, 0x80, 64, VIDEO_DEPTH_1BIT); else { - for (int d = VIDEO_DEPTH_1BIT; d <= default_depth; d++) { - int bpp = (d <= VIDEO_DEPTH_8BIT ? 8 : sdl_depth_of_video_depth(d)); - if (SDL_VideoModeOK(max_width, max_height, bpp, SDL_HWSURFACE)) - add_window_modes(video_depth(d)); + for (int i = 0; video_modes[i].w != 0; i++) { + const int w = video_modes[i].w; + const int h = video_modes[i].h; + if (i > 0 && (w >= default_width || h >= default_height)) + continue; + for (int d = VIDEO_DEPTH_1BIT; d <= default_depth; d++) + add_mode(display_type, w, h, video_modes[i].resolution_id, TrivialBytesPerRow(w, (video_depth)d), d); } } - } else - add_mode(display_type, default_width, default_height, 0x80, TrivialBytesPerRow(default_width, (video_depth)default_depth), default_depth); + } else if (display_type == DISPLAY_SCREEN) { + for (int i = 0; video_modes[i].w != 0; i++) { + const int w = video_modes[i].w; + const int h = video_modes[i].h; + if (i > 0 && (w >= default_width || h >= default_height)) + continue; + if (w == 512 && h == 384) + continue; + for (int d = VIDEO_DEPTH_1BIT; d <= default_depth; d++) + add_mode(display_type, w, h, video_modes[i].resolution_id, TrivialBytesPerRow(w, (video_depth)d), d); + } + } + if (VideoModes.empty()) { ErrorAlert(STR_NO_XVISUAL_ERR); return false; @@ -1031,24 +1316,25 @@ void SDL_monitor_desc::video_close(void) { D(bug("video_close()\n")); +#ifdef WIN32 + // Remove message handler for WM_DEVICECHANGE + HWND the_window = GetMainWindowHandle(); + SetWindowLongPtr(the_window, GWLP_WNDPROC, (LONG_PTR)sdl_window_proc); +#endif + // Stop redraw thread +#ifndef USE_CPU_EMUL_SERVICES if (redraw_thread_active) { redraw_thread_cancel = true; SDL_WaitThread(redraw_thread, NULL); } +#endif redraw_thread_active = false; // Unlock frame buffer UNLOCK_FRAME_BUFFER; D(bug(" frame buffer unlocked\n")); -#ifdef ENABLE_VOSF - if (use_vosf) { - // Deinitialize VOSF - video_vosf_exit(); - } -#endif - // Close display delete drv; drv = NULL; @@ -1066,6 +1352,8 @@ void VideoExit(void) SDL_DestroyMutex(frame_buffer_lock); if (sdl_palette_lock) SDL_DestroyMutex(sdl_palette_lock); + if (sdl_events_lock) + SDL_DestroyMutex(sdl_events_lock); } @@ -1207,8 +1495,11 @@ int16 video_mode_change(VidLocals *csSav csSave->saveData = ReadMacInt32(ParamPtr + csData); csSave->savePage = ReadMacInt16(ParamPtr + csPage); - // Disable interrupts + // Disable interrupts and pause redraw thread DisableInterrupt(); + thread_stop_ack = false; + thread_stop_req = true; + while (!thread_stop_ack) ; cur_mode = i; monitor_desc *monitor = VideoMonitors[0]; @@ -1219,7 +1510,8 @@ int16 video_mode_change(VidLocals *csSav csSave->saveData=VModes[cur_mode].viAppleID;/* First mode ... */ csSave->saveMode=VModes[cur_mode].viAppleMode; - // Enable interrupts + // Enable interrupts and resume redraw thread + thread_stop_req = false; EnableInterrupt(); return noErr; } @@ -1231,8 +1523,10 @@ int16 video_mode_change(VidLocals *csSav void SDL_monitor_desc::switch_to_current_mode(void) { // Close and reopen display + LOCK_EVENTS; video_close(); video_open(); + UNLOCK_EVENTS; if (drv == NULL) { ErrorAlert(STR_OPEN_WINDOW_ERR); @@ -1248,7 +1542,23 @@ void SDL_monitor_desc::switch_to_current #ifdef SHEEPSHAVER bool video_can_change_cursor(void) { - return (display_type == DISPLAY_WINDOW); + static char driver[] = "Quartz?"; + static int quartzok = -1; + + if (display_type != DISPLAY_WINDOW) + return false; + + if (quartzok < 0) { + if (SDL_VideoDriverName(driver, sizeof driver) == NULL || strncmp(driver, "Quartz", sizeof driver)) + quartzok = true; + else { + // Quartz driver bug prevents cursor changing in SDL 1.2.11 and later + const SDL_version *vp = SDL_Linked_Version(); + quartzok = SDL_VERSIONNUM(vp->major, vp->minor, vp->patch) <= SDL_VERSIONNUM(1, 2, 10); + } + } + + return quartzok; } #endif @@ -1335,23 +1645,23 @@ static int kc_decode(SDL_keysym const & case SDLK_1: case SDLK_EXCLAIM: return 0x12; case SDLK_2: case SDLK_AT: return 0x13; -// case SDLK_3: case SDLK_numbersign: return 0x14; + case SDLK_3: case SDLK_HASH: return 0x14; case SDLK_4: case SDLK_DOLLAR: return 0x15; -// case SDLK_5: case SDLK_percent: return 0x17; + case SDLK_5: return 0x17; case SDLK_6: return 0x16; case SDLK_7: return 0x1a; case SDLK_8: return 0x1c; case SDLK_9: return 0x19; case SDLK_0: return 0x1d; -// case SDLK_BACKQUOTE: case SDLK_asciitilde: return 0x0a; + case SDLK_BACKQUOTE: return 0x0a; case SDLK_MINUS: case SDLK_UNDERSCORE: return 0x1b; case SDLK_EQUALS: case SDLK_PLUS: return 0x18; -// case SDLK_bracketleft: case SDLK_braceleft: return 0x21; -// case SDLK_bracketright: case SDLK_braceright: return 0x1e; -// case SDLK_BACKSLASH: case SDLK_bar: return 0x2a; + case SDLK_LEFTBRACKET: return 0x21; + case SDLK_RIGHTBRACKET: return 0x1e; + case SDLK_BACKSLASH: return 0x2a; case SDLK_SEMICOLON: case SDLK_COLON: return 0x29; -// case SDLK_apostrophe: case SDLK_QUOTEDBL: return 0x27; + case SDLK_QUOTE: case SDLK_QUOTEDBL: return 0x27; case SDLK_COMMA: case SDLK_LESS: return 0x2b; case SDLK_PERIOD: case SDLK_GREATER: return 0x2f; case SDLK_SLASH: case SDLK_QUESTION: return 0x2c; @@ -1383,6 +1693,8 @@ static int kc_decode(SDL_keysym const & case SDLK_LMETA: return 0x3a; case SDLK_RMETA: return 0x3a; #endif + case SDLK_LSUPER: return 0x3a; // "Windows" key + case SDLK_RSUPER: return 0x3a; case SDLK_MENU: return 0x32; case SDLK_CAPSLOCK: return 0x39; case SDLK_NUMLOCK: return 0x47; @@ -1457,8 +1769,12 @@ static void handle_events(void) // Mouse button case SDL_MOUSEBUTTONDOWN: { unsigned int button = event.button.button; - if (button < 4) - ADBMouseDown(button - 1); + if (button == SDL_BUTTON_LEFT) + ADBMouseDown(0); + else if (button == SDL_BUTTON_RIGHT) + ADBMouseDown(1); + else if (button == SDL_BUTTON_MIDDLE) + ADBMouseDown(2); else if (button < 6) { // Wheel mouse if (mouse_wheel_mode == 0) { int key = (button == 5) ? 0x79 : 0x74; // Page up/down @@ -1476,8 +1792,12 @@ static void handle_events(void) } case SDL_MOUSEBUTTONUP: { unsigned int button = event.button.button; - if (button < 4) - ADBMouseUp(button - 1); + if (button == SDL_BUTTON_LEFT) + ADBMouseUp(0); + else if (button == SDL_BUTTON_RIGHT) + ADBMouseUp(1); + else if (button == SDL_BUTTON_MIDDLE) + ADBMouseUp(2); break; } @@ -1561,6 +1881,10 @@ static void handle_events(void) ADBKeyDown(0x7f); // Power key ADBKeyUp(0x7f); break; + + // Application activate/deactivate; consume the event but otherwise ignore it + case SDL_ACTIVEEVENT: + break; } } } @@ -1572,7 +1896,7 @@ static void handle_events(void) */ // Static display update (fixed frame rate, but incremental) -static void update_display_static(driver_window *drv) +static void update_display_static(driver_base *drv) { // Incremental update code int wide = 0, high = 0, x1, x2, y1, y2, i, j; @@ -1661,6 +1985,7 @@ static void update_display_static(driver } else { const int bytes_per_pixel = VIDEO_MODE_ROW_BYTES / VIDEO_MODE_X; + const int dst_bytes_per_row = drv->s->pitch; x1 = VIDEO_MODE_X; for (j=y1; j<=y2; j++) { @@ -1701,8 +2026,9 @@ static void update_display_static(driver // Blit to screen surface for (j=y1; j<=y2; j++) { i = j * bytes_per_row + x1 * bytes_per_pixel; + int dst_i = j * dst_bytes_per_row + x1 * bytes_per_pixel; memcpy(the_buffer_copy + i, the_buffer + i, bytes_per_pixel * wide); - Screen_blit((uint8 *)drv->s->pixels + i, the_buffer + i, bytes_per_pixel * wide); + Screen_blit((uint8 *)drv->s->pixels + dst_i, the_buffer + i, bytes_per_pixel * wide); } // Unlock surface, if required @@ -1716,6 +2042,67 @@ static void update_display_static(driver } } +// Static display update (fixed frame rate, bounding boxes based) +// XXX use NQD bounding boxes to help detect dirty areas? +static void update_display_static_bbox(driver_base *drv) +{ + const VIDEO_MODE &mode = drv->mode; + + // Allocate bounding boxes for SDL_UpdateRects() + const int N_PIXELS = 64; + const int n_x_boxes = (VIDEO_MODE_X + N_PIXELS - 1) / N_PIXELS; + const int n_y_boxes = (VIDEO_MODE_Y + N_PIXELS - 1) / N_PIXELS; + SDL_Rect *boxes = (SDL_Rect *)alloca(sizeof(SDL_Rect) * n_x_boxes * n_y_boxes); + int nr_boxes = 0; + + // Lock surface, if required + if (SDL_MUSTLOCK(drv->s)) + SDL_LockSurface(drv->s); + + // Update the surface from Mac screen + const int bytes_per_row = VIDEO_MODE_ROW_BYTES; + const int bytes_per_pixel = bytes_per_row / VIDEO_MODE_X; + const int dst_bytes_per_row = drv->s->pitch; + int x, y; + for (y = 0; y < VIDEO_MODE_Y; y += N_PIXELS) { + int h = N_PIXELS; + if (h > VIDEO_MODE_Y - y) + h = VIDEO_MODE_Y - y; + for (x = 0; x < VIDEO_MODE_X; x += N_PIXELS) { + int w = N_PIXELS; + if (w > VIDEO_MODE_X - x) + w = VIDEO_MODE_X - x; + const int xs = w * bytes_per_pixel; + const int xb = x * bytes_per_pixel; + bool dirty = false; + for (int j = y; j < (y + h); j++) { + const int yb = j * bytes_per_row; + const int dst_yb = j * dst_bytes_per_row; + if (memcmp(&the_buffer[yb + xb], &the_buffer_copy[yb + xb], xs) != 0) { + memcpy(&the_buffer_copy[yb + xb], &the_buffer[yb + xb], xs); + Screen_blit((uint8 *)drv->s->pixels + dst_yb + xb, the_buffer + yb + xb, xs); + dirty = true; + } + } + if (dirty) { + boxes[nr_boxes].x = x; + boxes[nr_boxes].y = y; + boxes[nr_boxes].w = w; + boxes[nr_boxes].h = h; + nr_boxes++; + } + } + } + + // Unlock surface, if required + if (SDL_MUSTLOCK(drv->s)) + SDL_UnlockSurface(drv->s); + + // Refresh display + if (nr_boxes) + SDL_UpdateRects(drv->s, nr_boxes, boxes); +} + // We suggest the compiler to inline the next two functions so that it // may specialise the code according to the current screen depth and @@ -1757,10 +2144,13 @@ static inline void handle_palette_change UNLOCK_PALETTE; } +static void video_refresh_window_static(void); + static void video_refresh_dga(void) { // Quit DGA mode if requested possibly_quit_dga_mode(); + video_refresh_window_static(); } #ifdef ENABLE_VOSF @@ -1776,7 +2166,7 @@ static void video_refresh_dga_vosf(void) tick_counter = 0; if (mainBuffer.dirty) { LOCK_VOSF; - update_display_dga_vosf(); + update_display_dga_vosf(static_cast(drv)); UNLOCK_VOSF; } } @@ -1810,7 +2200,11 @@ static void video_refresh_window_static( static int tick_counter = 0; if (++tick_counter >= frame_skip) { tick_counter = 0; - update_display_static(static_cast(drv)); + const VIDEO_MODE &mode = drv->mode; + if ((int)VIDEO_MODE_DEPTH >= VIDEO_DEPTH_8BIT) + update_display_static_bbox(drv); + else + update_display_static(drv); } } @@ -1840,9 +2234,64 @@ static void VideoRefreshInit(void) } } +static inline void do_video_refresh(void) +{ + // Handle SDL events + handle_events(); + + // Update display +#if (defined(__APPLE__) && defined(__MACH__)) + // SDL expects an auto-release pool to be present. + NSAutoReleasePool_wrap(video_refresh); +#else + video_refresh(); +#endif + +#ifdef SHEEPSHAVER + // Set new cursor image if it was changed + if (cursor_changed && sdl_cursor) { + cursor_changed = false; + LOCK_EVENTS; + SDL_FreeCursor(sdl_cursor); + sdl_cursor = SDL_CreateCursor(MacCursor + 4, MacCursor + 36, 16, 16, MacCursor[2], MacCursor[3]); + if (sdl_cursor) { + SDL_ShowCursor(private_data == NULL || private_data->cursorVisible); + SDL_SetCursor(sdl_cursor); +#ifdef WIN32 + // XXX Windows apparently needs an extra mouse event to + // make the new cursor image visible + int visible = SDL_ShowCursor(-1); + if (visible) { + int x, y; + SDL_GetMouseState(&x, &y); + SDL_WarpMouse(x, y); + } +#endif + } + UNLOCK_EVENTS; + } +#endif + + // Set new palette if it was changed + handle_palette_changes(); +} + +// This function is called on non-threaded platforms from a timer interrupt +void VideoRefresh(void) +{ + // We need to check redraw_thread_active to inhibit refreshed during + // mode changes on non-threaded platforms + if (!redraw_thread_active) + return; + + // Process pending events and update display + do_video_refresh(); +} + const int VIDEO_REFRESH_HZ = 60; const int VIDEO_REFRESH_DELAY = 1000000 / VIDEO_REFRESH_HZ; +#ifndef USE_CPU_EMUL_SERVICES static int redraw_func(void *arg) { uint64 start = GetTicks_usec(); @@ -1860,28 +2309,44 @@ static int redraw_func(void *arg) next = GetTicks_usec(); ticks++; - // Handle SDL events - handle_events(); - - // Refresh display - video_refresh(); - #ifdef SHEEPSHAVER - // Set new cursor image if it was changed - if (cursor_changed && sdl_cursor) { - cursor_changed = false; - SDL_FreeCursor(sdl_cursor); - sdl_cursor = SDL_CreateCursor(MacCursor + 4, MacCursor + 36, 16, 16, MacCursor[2], MacCursor[3]); - if (sdl_cursor) - SDL_SetCursor(sdl_cursor); + // Pause if requested (during video mode switches) + if (thread_stop_req) { + thread_stop_ack = true; + continue; } #endif - // Set new palette if it was changed - handle_palette_changes(); + // Process pending events and update display + do_video_refresh(); } uint64 end = GetTicks_usec(); D(bug("%lld refreshes in %lld usec = %f refreshes/sec\n", ticks, end - start, ticks * 1000000.0 / (end - start))); return 0; } +#endif + + +/* + * Record dirty area from NQD + */ + +#ifdef SHEEPSHAVER +void video_set_dirty_area(int x, int y, int w, int h) +{ +#ifdef ENABLE_VOSF + const VIDEO_MODE &mode = drv->mode; + const int screen_width = VIDEO_MODE_X; + const int screen_height = VIDEO_MODE_Y; + const int bytes_per_row = VIDEO_MODE_ROW_BYTES; + + if (use_vosf) { + vosf_set_dirty_area(x, y, w, h, screen_width, screen_height, bytes_per_row); + return; + } +#endif + + // XXX handle dirty bounding boxes for non-VOSF modes +} +#endif