--- BasiliskII/src/SDL/video_sdl.cpp 2004/06/23 22:37:33 1.2 +++ 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,20 +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. + * - Backport hw cursor acceleration to Basilisk II? + * - Factor out code */ #include "sysdeps.h" @@ -43,6 +46,11 @@ #include #include #include +#include + +#ifdef WIN32 +#include /* alloca() */ +#endif #include "cpu_emulation.h" #include "main.h" @@ -51,23 +59,42 @@ #include "prefs.h" #include "user_strings.h" #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 -static vector VideoModes; +using std::vector; +static vector VideoModes; // Display types +#ifdef SHEEPSHAVER enum { - DISPLAY_WINDOW, // windowed display - DISPLAY_SCREEN // fullscreen display + DISPLAY_WINDOW = DIS_WINDOW, // windowed display + DISPLAY_SCREEN = DIS_SCREEN // fullscreen display }; +extern int display_type; // See enum above +#else +enum { + DISPLAY_WINDOW, // windowed display + DISPLAY_SCREEN // fullscreen display +}; +static int display_type = DISPLAY_WINDOW; // See enum above +#endif // Constants +#ifdef WIN32 +const char KEYCODE_FILE_NAME[] = "BasiliskII_keycodes"; +#else const char KEYCODE_FILE_NAME[] = DATADIR "/keycodes"; +#endif // Global variables @@ -75,17 +102,22 @@ static int32 frame_skip; // Prefs static int16 mouse_wheel_mode; static int16 mouse_wheel_lines; -static int display_type = DISPLAY_WINDOW; // See enum above static uint8 *the_buffer = NULL; // Mac frame buffer (where MacOS draws into) static uint8 *the_buffer_copy = NULL; // Copy of Mac frame buffer (for refreshed modes) 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 = true; // Flag: VOSF enabled +static bool use_vosf = false; // Flag: VOSF enabled #else static const bool use_vosf = false; // VOSF not possible #endif @@ -103,9 +135,16 @@ static int keycode_table[256]; // X // SDL variables static int screen_depth; // Depth of current screen +static SDL_Cursor *sdl_cursor; // Copy of Mac cursor +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; @@ -130,12 +169,179 @@ 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 + */ + +#ifdef SHEEPSHAVER +// Color depth modes type +typedef int video_depth; + +// 1, 2, 4 and 8 bit depths use a color palette +static inline bool IsDirectMode(VIDEO_MODE const & mode) +{ + return IsDirectMode(mode.viAppleMode); +} + +// Abstract base class representing one (possibly virtual) monitor +// ("monitor" = rectangular display with a contiguous frame buffer) +class monitor_desc { +public: + monitor_desc(const vector &available_modes, video_depth default_depth, uint32 default_id) {} + virtual ~monitor_desc() {} + + // Get current Mac frame buffer base address + uint32 get_mac_frame_base(void) const {return screen_base;} + + // Set Mac frame buffer base address (called from switch_to_mode()) + void set_mac_frame_base(uint32 base) {screen_base = base;} + + // Get current video mode + const VIDEO_MODE &get_current_mode(void) const {return VModes[cur_mode];} + + // Called by the video driver to switch the video mode on this display + // (must call set_mac_frame_base()) + virtual void switch_to_current_mode(void) = 0; + + // Called by the video driver to set the color palette (in indexed modes) + // or the gamma table (in direct modes) + virtual void set_palette(uint8 *pal, int num) = 0; +}; + +// Vector of pointers to available monitor descriptions, filled by VideoInit() +static vector VideoMonitors; + +// Find Apple mode matching best specified dimensions +static int find_apple_resolution(int xsize, int ysize) +{ + 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 +static void ErrorAlert(int error) +{ + ErrorAlert(GetString(error)); +} + +// Display warning alert +static void WarningAlert(int warning) +{ + WarningAlert(GetString(warning)); +} +#endif + + +/* * monitor_desc subclass for SDL display */ class SDL_monitor_desc : public monitor_desc { public: - SDL_monitor_desc(const vector &available_modes, video_depth default_depth, uint32 default_id) : monitor_desc(available_modes, default_depth, default_id) {} + SDL_monitor_desc(const vector &available_modes, video_depth default_depth, uint32 default_id) : monitor_desc(available_modes, default_depth, default_id) {} ~SDL_monitor_desc() {} virtual void switch_to_current_mode(void); @@ -150,27 +356,61 @@ public: * Utility functions */ +// Find palette size for given color depth +static int palette_size(int mode) +{ + switch (mode) { + case VIDEO_DEPTH_1BIT: return 2; + case VIDEO_DEPTH_2BIT: return 4; + case VIDEO_DEPTH_4BIT: return 16; + case VIDEO_DEPTH_8BIT: return 256; + case VIDEO_DEPTH_16BIT: return 32; + case VIDEO_DEPTH_32BIT: return 256; + default: return 0; + } +} + +// Return bytes per pixel for requested depth +static inline int bytes_per_pixel(int depth) +{ + int bpp; + switch (depth) { + case 8: + bpp = 1; + break; + case 15: case 16: + bpp = 2; + break; + case 24: case 32: + bpp = 4; + break; + default: + abort(); + } + return bpp; +} + // 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) { - case VDEPTH_1BIT: + case VIDEO_DEPTH_1BIT: depth = 1; break; - case VDEPTH_2BIT: + case VIDEO_DEPTH_2BIT: depth = 2; break; - case VDEPTH_4BIT: + case VIDEO_DEPTH_4BIT: depth = 4; break; - case VDEPTH_8BIT: + case VIDEO_DEPTH_8BIT: depth = 8; break; - case VDEPTH_16BIT: + case VIDEO_DEPTH_16BIT: depth = 16; break; - case VDEPTH_32BIT: + case VIDEO_DEPTH_32BIT: depth = 32; break; default: @@ -179,38 +419,98 @@ static int sdl_depth_of_video_depth(int return depth; } -// Add mode to list of supported modes -static void add_mode(uint32 width, uint32 height, uint32 resolution_id, uint32 bytes_per_row, video_depth depth) +// Map video_mode depth ID to SDL screen depth +static int sdl_depth_of_video_depth(int video_depth) { - video_mode mode; - mode.x = width; - mode.y = height; - mode.resolution_id = resolution_id; - mode.bytes_per_row = bytes_per_row; - mode.depth = depth; - VideoModes.push_back(mode); + return (video_depth <= VIDEO_DEPTH_8BIT) ? 8 : mac_depth_of_video_depth(video_depth); } -// Add standard list of windowed modes for given color depth -static void add_window_modes(video_depth depth) +// Get screen dimensions +static void sdl_display_dimensions(int &width, int &height) { - add_mode(512, 384, 0x80, TrivialBytesPerRow(512, depth), depth); - add_mode(640, 480, 0x81, TrivialBytesPerRow(640, depth), depth); - add_mode(800, 600, 0x82, TrivialBytesPerRow(800, depth), depth); - add_mode(1024, 768, 0x83, TrivialBytesPerRow(1024, depth), depth); - add_mode(1152, 870, 0x84, TrivialBytesPerRow(1152, depth), depth); - add_mode(1280, 1024, 0x85, TrivialBytesPerRow(1280, depth), depth); - add_mode(1600, 1200, 0x86, TrivialBytesPerRow(1600, depth), depth); + 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 resolutions + if (width == 512 && height == 384) + return false; +#endif + + // 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, depth)) + return; + + // Fill in VideoMode entry + VIDEO_MODE mode; +#ifdef SHEEPSHAVER + resolution_id = find_apple_resolution(width, height); + mode.viType = type; +#endif + VIDEO_MODE_X = width; + VIDEO_MODE_Y = height; + VIDEO_MODE_RESOLUTION = resolution_id; + VIDEO_MODE_ROW_BYTES = bytes_per_row; + VIDEO_MODE_DEPTH = (video_depth)depth; + VideoModes.push_back(mode); } // Set Mac frame layout and base address (uses the_buffer/MacFrameBaseMac) -static void set_mac_frame_buffer(SDL_monitor_desc &monitor, video_depth depth, bool native_byte_order) +static void set_mac_frame_buffer(SDL_monitor_desc &monitor, int depth, bool native_byte_order) { #if !REAL_ADDRESSING && !DIRECT_ADDRESSING int layout = FLAYOUT_DIRECT; - if (depth == VDEPTH_16BIT) + if (depth == VIDEO_DEPTH_16BIT) layout = (screen_depth == 15) ? FLAYOUT_HOST_555 : FLAYOUT_HOST_565; - else if (depth == VDEPTH_32BIT) + else if (depth == VIDEO_DEPTH_32BIT) layout = (screen_depth == 24) ? FLAYOUT_HOST_888 : FLAYOUT_DIRECT; if (native_byte_order) MacFrameLayout = layout; @@ -219,9 +519,9 @@ static void set_mac_frame_buffer(SDL_mon monitor.set_mac_frame_base(MacFrameBaseMac); // Set variables used by UAE memory banking - const video_mode &mode = monitor.get_current_mode(); + const VIDEO_MODE &mode = monitor.get_current_mode(); MacFrameBaseHost = the_buffer; - MacFrameSize = mode.bytes_per_row * mode.y; + MacFrameSize = VIDEO_MODE_ROW_BYTES * VIDEO_MODE_Y; InitFrameBufferMapping(); #else monitor.set_mac_frame_base(Host2MacAddr(the_buffer)); @@ -242,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 @@ -270,7 +619,7 @@ public: public: SDL_monitor_desc &monitor; // Associated video monitor - const video_mode &mode; // Video mode handled by the driver + const VIDEO_MODE &mode; // Video mode handled by the driver bool init_ok; // Initialization succeeded (we can't use exceptions because of -fomit-frame-pointer) SDL_Surface *s; // The surface we draw into @@ -279,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); @@ -301,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 @@ -322,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; @@ -335,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); @@ -351,6 +703,9 @@ driver_base::~driver_base() free(the_buffer_copy); the_buffer_copy = NULL; } + + // Deinitialize VOSF + video_vosf_exit(); } #endif } @@ -358,9 +713,9 @@ driver_base::~driver_base() // Palette has changed void driver_base::update_palette(void) { - const video_mode &mode = monitor.get_current_mode(); + const VIDEO_MODE &mode = monitor.get_current_mode(); - if (mode.depth <= VDEPTH_8BIT) + if ((int)VIDEO_MODE_DEPTH <= VIDEO_DEPTH_8BIT) SDL_SetPalette(s, SDL_PHYSPAL, sdl_palette, 0, 256); } @@ -379,44 +734,83 @@ 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) { - int width = mode.x, height = mode.y; + 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(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 = (mode.depth <= VDEPTH_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)); + + // 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)); + } + +#ifdef SHEEPSHAVER + // Create cursor + if ((sdl_cursor = SDL_CreateCursor(MacCursor + 4, MacCursor + 36, 16, 16, 0, 0)) != NULL) { + SDL_SetCursor(sdl_cursor); + cursor_changed = false; + } #else - // 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); - D(bug("the_buffer = %p, the_buffer_copy = %p\n", the_buffer, the_buffer_copy)); + // Hide cursor + SDL_ShowCursor(0); #endif // Set window name/class set_window_name(STR_WINDOW_TITLE); - // Hide cursor - SDL_ShowCursor(0); - // Init blitting routines SDL_PixelFormat *f = s->format; VisualFormat visualFormat; @@ -424,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(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)) @@ -432,7 +826,7 @@ driver_window::driver_window(SDL_monitor ExpandMap[i] = SDL_MapRGB(f, i, i, i); // Set frame buffer base - set_mac_frame_buffer(monitor, mode.depth, true); + set_mac_frame_buffer(monitor, VIDEO_MODE_DEPTH, true); // Everything went well init_ok = true; @@ -491,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 */ @@ -522,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); @@ -534,22 +1022,23 @@ static void keycode_init(void) continue; if (video_driver_found) { - // Skip aliases - static const char alias_str[] = "alias"; - if (strncmp(line, alias_str, sizeof(alias_str) - 1) == 0) + // 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 && 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 { // Search for SDL video driver string - static const char alias_sdl_str[] = "alias SDL"; - if (strncmp(line, alias_sdl_str, sizeof(alias_sdl_str) - 1) == 0) { - char *p = line + sizeof(alias_sdl_str); + static const char sdl_str[] = "sdl"; + if (strncmp(line, sdl_str, sizeof(sdl_str) - 1) == 0) { + char *p = line + sizeof(sdl_str); if (strstr(video_driver, p) == video_driver) video_driver_found = true; } @@ -567,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)); } } @@ -574,13 +1065,20 @@ static void keycode_init(void) bool SDL_monitor_desc::video_open(void) { D(bug("video_open()\n")); - const video_mode &mode = get_current_mode(); + const VIDEO_MODE &mode = get_current_mode(); +#if DEBUG + D(bug("Current video mode:\n")); + D(bug(" %dx%d (ID %02x), %d bpp\n", VIDEO_MODE_X, VIDEO_MODE_Y, VIDEO_MODE_RESOLUTION, 1 << (VIDEO_MODE_DEPTH & 0x0f))); +#endif // Create display driver object of requested type switch (display_type) { 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; @@ -590,16 +1088,13 @@ bool SDL_monitor_desc::video_open(void) return false; } -#ifdef ENABLE_VOSF - if (use_vosf) { - // Initialize the VOSF system - if (!video_vosf_init(*this)) { - ErrorAlert(STR_VOSF_INIT_ERR); - 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(); @@ -607,17 +1102,27 @@ 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 = (SDL_CreateThread(redraw_func, NULL) != NULL); + 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; } +#ifdef SHEEPSHAVER +bool VideoInit(void) +{ + const bool classic = false; +#else bool VideoInit(bool classic) { +#endif classic_mode = classic; #ifdef ENABLE_VOSF @@ -627,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) @@ -641,63 +1148,104 @@ bool VideoInit(bool classic) mouse_wheel_lines = PrefsFindInt32("mousewheellines"); // Get screen mode from preferences - const char *mode_str; + migrate_screen_prefs(); + const char *mode_str = NULL; if (classic_mode) mode_str = "win/512/342"; else mode_str = PrefsFindString("screen"); // Determine display type and default dimensions - int default_width = 512, default_height = 384; + int default_width, default_height; + if (classic) { + default_width = 512; + default_height = 384; + } + else { + default_width = 640; + default_height = 480; + } display_type = DISPLAY_WINDOW; 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; - if (display_type == DISPLAY_SCREEN) { - 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; - } + 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; - video_depth default_depth; + int default_depth; switch (screen_depth) { case 8: - default_depth = VDEPTH_8BIT; + default_depth = VIDEO_DEPTH_8BIT; break; case 15: case 16: - default_depth = VDEPTH_16BIT; + default_depth = VIDEO_DEPTH_16BIT; break; case 24: case 32: - default_depth = VDEPTH_32BIT; + default_depth = VIDEO_DEPTH_32BIT; break; default: - default_depth = VDEPTH_1BIT; + default_depth = VIDEO_DEPTH_1BIT; 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(512, 342, 0x80, 64, VDEPTH_1BIT); + add_mode(display_type, 512, 342, 0x80, 64, VIDEO_DEPTH_1BIT); else { - for (int d = VDEPTH_1BIT; d <= default_depth; d++) { - int bpp = (d <= VDEPTH_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(default_width, default_height, 0x80, TrivialBytesPerRow(default_width, 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; @@ -705,32 +1253,53 @@ bool VideoInit(bool classic) // Find requested default mode with specified dimensions uint32 default_id; - std::vector::const_iterator i, end = VideoModes.end(); + std::vector::const_iterator i, end = VideoModes.end(); for (i = VideoModes.begin(); i != end; ++i) { - if (i->x == default_width && i->y == default_height && i->depth == default_depth) { - default_id = i->resolution_id; + const VIDEO_MODE & mode = (*i); + if (VIDEO_MODE_X == default_width && VIDEO_MODE_Y == default_height && VIDEO_MODE_DEPTH == default_depth) { + default_id = VIDEO_MODE_RESOLUTION; +#ifdef SHEEPSHAVER + std::vector::const_iterator begin = VideoModes.begin(); + cur_mode = distance(begin, i); +#endif break; } } if (i == end) { // not found, use first available mode - default_depth = VideoModes[0].depth; - default_id = VideoModes[0].resolution_id; + const VIDEO_MODE & mode = VideoModes[0]; + default_depth = VIDEO_MODE_DEPTH; + default_id = VIDEO_MODE_RESOLUTION; +#ifdef SHEEPSHAVER + cur_mode = 0; +#endif } +#ifdef SHEEPSHAVER + for (int i = 0; i < VideoModes.size(); i++) + VModes[i] = VideoModes[i]; + VideoInfo *p = &VModes[VideoModes.size()]; + p->viType = DIS_INVALID; // End marker + p->viRowBytes = 0; + p->viXsize = p->viYsize = 0; + p->viAppleMode = 0; + p->viAppleID = 0; +#endif + #if DEBUG D(bug("Available video modes:\n")); for (i = VideoModes.begin(); i != end; ++i) { - int bits = 1 << i->depth; + const VIDEO_MODE & mode = (*i); + int bits = 1 << VIDEO_MODE_DEPTH; if (bits == 16) bits = 15; else if (bits == 32) bits = 24; - D(bug(" %dx%d (ID %02x), %d colors\n", i->x, i->y, i->resolution_id, 1 << bits)); + D(bug(" %dx%d (ID %02x), %d colors\n", VIDEO_MODE_X, VIDEO_MODE_Y, VIDEO_MODE_RESOLUTION, 1 << bits)); } #endif // Create SDL_monitor_desc for this (the only) display - SDL_monitor_desc *monitor = new SDL_monitor_desc(VideoModes, default_depth, default_id); + SDL_monitor_desc *monitor = new SDL_monitor_desc(VideoModes, (video_depth)default_depth, default_id); VideoMonitors.push_back(monitor); // Open display @@ -747,25 +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); doesn't work - while (redraw_thread_cancel); + 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; @@ -783,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); } @@ -801,6 +1372,27 @@ void VideoQuitFullScreen(void) * Mac VBL interrupt */ +/* + * Execute video VBL routine + */ + +#ifdef SHEEPSHAVER +void VideoVBL(void) +{ + // Emergency quit requested? Then quit + if (emerg_quit) + QuitEmulator(); + + // Temporarily give up frame buffer lock (this is the point where + // we are suspended when the user presses Ctrl-Tab) + UNLOCK_FRAME_BUFFER; + LOCK_FRAME_BUFFER; + + // Execute video VBL + if (private_data != NULL && private_data->interruptsEnabled) + VSLDoInterruptService(private_data->vslServiceID); +} +#else void VideoInterrupt(void) { // We must fill in the events queue in the same thread that did call SDL_SetVideoMode() @@ -815,18 +1407,34 @@ void VideoInterrupt(void) UNLOCK_FRAME_BUFFER; LOCK_FRAME_BUFFER; } +#endif /* * Set palette */ +#ifdef SHEEPSHAVER +void video_set_palette(void) +{ + monitor_desc * monitor = VideoMonitors[0]; + int n_colors = palette_size(monitor->get_current_mode().viAppleMode); + uint8 pal[256 * 3]; + for (int c = 0; c < n_colors; c++) { + pal[c*3 + 0] = mac_pal[c].red; + pal[c*3 + 1] = mac_pal[c].green; + pal[c*3 + 2] = mac_pal[c].blue; + } + monitor->set_palette(pal, n_colors); +} +#endif + void SDL_monitor_desc::set_palette(uint8 *pal, int num_in) { - const video_mode &mode = get_current_mode(); + const VIDEO_MODE &mode = get_current_mode(); // FIXME: how can we handle the gamma ramp? - if (mode.depth > VDEPTH_8BIT) + if ((int)VIDEO_MODE_DEPTH > VIDEO_DEPTH_8BIT) return; LOCK_PALETTE; @@ -851,11 +1459,13 @@ void SDL_monitor_desc::set_palette(uint8 } #ifdef ENABLE_VOSF - // We have to redraw everything because the interpretation of pixel values changed - LOCK_VOSF; - PFLAG_SET_ALL; - UNLOCK_VOSF; - memset(the_buffer_copy, 0, mode.bytes_per_row * mode.y); + if (use_vosf) { + // We have to redraw everything because the interpretation of pixel values changed + LOCK_VOSF; + PFLAG_SET_ALL; + UNLOCK_VOSF; + memset(the_buffer_copy, 0, VIDEO_MODE_ROW_BYTES * VIDEO_MODE_Y); + } #endif } @@ -870,11 +1480,53 @@ void SDL_monitor_desc::set_palette(uint8 * Switch video mode */ +#ifdef SHEEPSHAVER +int16 video_mode_change(VidLocals *csSave, uint32 ParamPtr) +{ + /* return if no mode change */ + if ((csSave->saveData == ReadMacInt32(ParamPtr + csData)) && + (csSave->saveMode == ReadMacInt16(ParamPtr + csMode))) return noErr; + + /* first find video mode in table */ + for (int i=0; VModes[i].viType != DIS_INVALID; i++) { + if ((ReadMacInt16(ParamPtr + csMode) == VModes[i].viAppleMode) && + (ReadMacInt32(ParamPtr + csData) == VModes[i].viAppleID)) { + csSave->saveMode = ReadMacInt16(ParamPtr + csMode); + csSave->saveData = ReadMacInt32(ParamPtr + csData); + csSave->savePage = ReadMacInt16(ParamPtr + csPage); + + // 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]; + monitor->switch_to_current_mode(); + + WriteMacInt32(ParamPtr + csBaseAddr, screen_base); + csSave->saveBaseAddr=screen_base; + csSave->saveData=VModes[cur_mode].viAppleID;/* First mode ... */ + csSave->saveMode=VModes[cur_mode].viAppleMode; + + // Enable interrupts and resume redraw thread + thread_stop_req = false; + EnableInterrupt(); + return noErr; + } + } + return paramErr; +} +#endif + 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); @@ -884,15 +1536,83 @@ void SDL_monitor_desc::switch_to_current /* - * Translate key event to Mac keycode, returns -1 if no keycode was found - * and -2 if the key was recognized as a hotkey + * Can we set the MacOS cursor image into the window? + */ + +#ifdef SHEEPSHAVER +bool video_can_change_cursor(void) +{ + 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 + + +/* + * Set cursor image for window + */ + +#ifdef SHEEPSHAVER +void video_set_cursor(void) +{ + cursor_changed = true; +} +#endif + + +/* + * Keyboard-related utilify functions */ +static bool is_modifier_key(SDL_KeyboardEvent const & e) +{ + switch (e.keysym.sym) { + case SDLK_NUMLOCK: + case SDLK_CAPSLOCK: + case SDLK_SCROLLOCK: + case SDLK_RSHIFT: + case SDLK_LSHIFT: + case SDLK_RCTRL: + case SDLK_LCTRL: + case SDLK_RALT: + case SDLK_LALT: + case SDLK_RMETA: + case SDLK_LMETA: + case SDLK_LSUPER: + case SDLK_RSUPER: + case SDLK_MODE: + case SDLK_COMPOSE: + return true; + } + return false; +} + static bool is_ctrl_down(SDL_keysym const & ks) { return ctrl_down || (ks.mod & KMOD_CTRL); } + +/* + * Translate key event to Mac keycode, returns -1 if no keycode was found + * and -2 if the key was recognized as a hotkey + */ + static int kc_decode(SDL_keysym const & ks, bool key_down) { switch (ks.sym) { @@ -925,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; @@ -962,10 +1682,19 @@ static int kc_decode(SDL_keysym const & case SDLK_RCTRL: return 0x36; case SDLK_LSHIFT: return 0x38; case SDLK_RSHIFT: return 0x38; +#if (defined(__APPLE__) && defined(__MACH__)) + case SDLK_LALT: return 0x3a; + case SDLK_RALT: return 0x3a; + case SDLK_LMETA: return 0x37; + case SDLK_RMETA: return 0x37; +#else case SDLK_LALT: return 0x37; case SDLK_RALT: return 0x37; 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; @@ -1040,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 @@ -1059,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; } @@ -1072,7 +1809,7 @@ static void handle_events(void) // Keyboard case SDL_KEYDOWN: { int code = -1; - if (use_keycodes) { + if (use_keycodes && !is_modifier_key(event.key)) { if (event2keycode(event.key, true) != -2) // This is called to process the hotkeys code = keycode_table[event.key.keysym.scancode & 0xff]; } else @@ -1100,13 +1837,22 @@ static void handle_events(void) } case SDL_KEYUP: { int code = -1; - if (use_keycodes) { + if (use_keycodes && !is_modifier_key(event.key)) { if (event2keycode(event.key, false) != -2) // This is called to process the hotkeys code = keycode_table[event.key.keysym.scancode & 0xff]; } else code = event2keycode(event.key, false); - if (code >= 0 && code != 0x39) { // Don't propagate Caps Lock releases - ADBKeyUp(code); + if (code >= 0) { + if (code == 0x39) { // Caps Lock released + if (caps_on) { + ADBKeyUp(code); + caps_on = false; + } else { + ADBKeyDown(code); + caps_on = true; + } + } else + ADBKeyUp(code); if (code == 0x36) ctrl_down = false; } @@ -1116,17 +1862,17 @@ static void handle_events(void) // Hidden parts exposed, force complete refresh of window case SDL_VIDEOEXPOSE: if (display_type == DISPLAY_WINDOW) { - const video_mode &mode = VideoMonitors[0]->get_current_mode(); + const VIDEO_MODE &mode = VideoMonitors[0]->get_current_mode(); #ifdef ENABLE_VOSF if (use_vosf) { // VOSF refresh LOCK_VOSF; PFLAG_SET_ALL; UNLOCK_VOSF; - memset(the_buffer_copy, 0, mode.bytes_per_row * mode.y); + memset(the_buffer_copy, 0, VIDEO_MODE_ROW_BYTES * VIDEO_MODE_Y); } else #endif - memset(the_buffer_copy, 0, mode.bytes_per_row * mode.y); + memset(the_buffer_copy, 0, VIDEO_MODE_ROW_BYTES * VIDEO_MODE_Y); } break; @@ -1135,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; } } } @@ -1146,24 +1896,24 @@ 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; - const video_mode &mode = drv->mode; - int bytes_per_row = mode.bytes_per_row; + const VIDEO_MODE &mode = drv->mode; + int bytes_per_row = VIDEO_MODE_ROW_BYTES; uint8 *p, *p2; // Check for first line from top and first line from bottom that have changed y1 = 0; - for (j=0; j=y1; j--) { + for (j=VIDEO_MODE_Y-1; j>=y1; j--) { if (memcmp(&the_buffer[j * bytes_per_row], &the_buffer_copy[j * bytes_per_row], bytes_per_row)) { y2 = j; break; @@ -1173,12 +1923,12 @@ static void update_display_static(driver // Check for first column from left and first column from right that have changed if (high) { - if (mode.depth < VDEPTH_8BIT) { + if ((int)VIDEO_MODE_DEPTH < VIDEO_DEPTH_8BIT) { const int src_bytes_per_row = bytes_per_row; const int dst_bytes_per_row = drv->s->pitch; - const int pixels_per_byte = mode.x / src_bytes_per_row; + const int pixels_per_byte = VIDEO_MODE_X / src_bytes_per_row; - x1 = mode.x / pixels_per_byte; + x1 = VIDEO_MODE_X / pixels_per_byte; for (j = y1; j <= y2; j++) { p = &the_buffer[j * bytes_per_row]; p2 = &the_buffer_copy[j * bytes_per_row]; @@ -1196,7 +1946,7 @@ static void update_display_static(driver p2 = &the_buffer_copy[j * bytes_per_row]; p += bytes_per_row; p2 += bytes_per_row; - for (i = (mode.x / pixels_per_byte); i > x2; i--) { + for (i = (VIDEO_MODE_X / pixels_per_byte); i > x2; i--) { p--; p2--; if (*p != *p2) { x2 = i; @@ -1234,9 +1984,10 @@ static void update_display_static(driver } } else { - const int bytes_per_pixel = mode.bytes_per_row / mode.x; + const int bytes_per_pixel = VIDEO_MODE_ROW_BYTES / VIDEO_MODE_X; + const int dst_bytes_per_row = drv->s->pitch; - x1 = mode.x; + x1 = VIDEO_MODE_X; for (j=y1; j<=y2; j++) { p = &the_buffer[j * bytes_per_row]; p2 = &the_buffer_copy[j * bytes_per_row]; @@ -1254,7 +2005,7 @@ static void update_display_static(driver p2 = &the_buffer_copy[j * bytes_per_row]; p += bytes_per_row; p2 += bytes_per_row; - for (i=mode.x*bytes_per_pixel; i>x2*bytes_per_pixel; i--) { + for (i=VIDEO_MODE_X*bytes_per_pixel; i>x2*bytes_per_pixel; i--) { p--; p2--; if (*p != *p2) { @@ -1275,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 @@ -1290,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 @@ -1331,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 @@ -1350,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; } } @@ -1384,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); } } @@ -1414,29 +2234,119 @@ 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(); int64 ticks = 0; + uint64 next = GetTicks_usec() + VIDEO_REFRESH_DELAY; while (!redraw_thread_cancel) { // Wait - Delay_usec(16667); - - // Handle SDL events - handle_events(); - - // Refresh display - video_refresh(); + next += VIDEO_REFRESH_DELAY; + int64 delay = next - GetTicks_usec(); + if (delay > 0) + Delay_usec(delay); + else if (delay < -VIDEO_REFRESH_DELAY) + next = GetTicks_usec(); ticks++; - // Set new palette if it was changed - handle_palette_changes(); +#ifdef SHEEPSHAVER + // Pause if requested (during video mode switches) + if (thread_stop_req) { + thread_stop_ack = true; + continue; + } +#endif + + // 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))); - redraw_thread_cancel = false; 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