[gens-sdl] Split the emulation loop and "Crazy" Effect loop into separate files.
authorDavid Korth <gerbilsoft@gerbilsoft.com>
Thu, 3 Sep 2015 01:22:17 +0000 (21:22 -0400)
committerDavid Korth <gerbilsoft@gerbilsoft.com>
Thu, 3 Sep 2015 01:22:17 +0000 (21:22 -0400)
All of the functions are currently in the GensSdl namespace without
any enclosing classes.

EmuLoop.cpp has its own processSdlEvent_emuLoop() function to handle keys
that aren't needed for the "Crazy" Effect, e.g. savestates. If an event
isn't handled by EmuLoop, gens-sdl.cpp's processSdlEvent_common() will
try processing it. If that doesn't handle it, the key will be sent
to the KeyManager.

TODO: Convert EmuLoop and CrazyEffectLoop to classes with a common
base class.

src/gens-sdl/CMakeLists.txt
src/gens-sdl/CrazyEffectLoop.cpp [new file with mode: 0644]
src/gens-sdl/CrazyEffectLoop.hpp [new file with mode: 0644]
src/gens-sdl/EmuLoop.cpp [new file with mode: 0644]
src/gens-sdl/EmuLoop.hpp [new file with mode: 0644]
src/gens-sdl/gens-sdl.cpp
src/gens-sdl/gens-sdl.hpp [new file with mode: 0644]

index 59a89b9..20dc947 100644 (file)
@@ -32,6 +32,8 @@ CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.gens-sdl.h.in" "${CMAKE_CURRE
 # Sources.
 SET(gens-sdl_SRCS
        gens-sdl.cpp
+       EmuLoop.cpp
+       CrazyEffectLoop.cpp
        SdlHandler.cpp
        SdlHandler_scancode.cpp
        RingBuffer.cpp
@@ -46,7 +48,9 @@ SET(gens-sdl_SRCS
 
 # Headers.
 SET(gens-sdl_H
-       gens-sdl.cpp
+       gens-sdl.hpp
+       EmuLoop.hpp
+       CrazyEffectLoop.hpp
        SdlHandler.hpp
        RingBuffer.hpp
        Config.hpp
diff --git a/src/gens-sdl/CrazyEffectLoop.cpp b/src/gens-sdl/CrazyEffectLoop.cpp
new file mode 100644 (file)
index 0000000..7283747
--- /dev/null
@@ -0,0 +1,202 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * CrazyEffectLoop.hpp: "Crazy" Effect loop.                               *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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 the   *
+ * Free Software Foundation; either version 2 of the License, or (at your  *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of              *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+// Reentrant functions.
+// MUST be included before everything else due to
+// _POSIX_SOURCE and _POSIX_C_SOURCE definitions.
+#include "libcompat/reentrant.h"
+
+#include "CrazyEffectLoop.hpp"
+#include "gens-sdl.hpp"
+
+#include "SdlHandler.hpp"
+#include "VBackend.hpp"
+using GensSdl::SdlHandler;
+using GensSdl::VBackend;
+
+// LibGens
+#include "libgens/Rom.hpp"
+#include "libgens/Util/MdFb.hpp"
+using LibGens::Rom;
+using LibGens::MdFb;
+
+// "Crazy" Effect.
+#include "libgens/Effects/CrazyEffect.hpp"
+using LibGens::CrazyEffect;
+
+// OS-specific includes.
+#ifdef _WIN32
+// Windows
+#include <windows.h>
+// Win32 Unicode Translation Layer.
+// Needed for proper Unicode filename support on Windows.
+#include "libcompat/W32U/W32U_mini.h"
+#include "libcompat/W32U/W32U_argv.h"
+#else
+// Linux, Unix, Mac OS X
+#include <unistd.h>
+#endif
+
+// yield(), aka usleep(0) or Sleep(0)
+#ifdef _WIN32
+// Windows
+#define yield() do { Sleep(0); } while (0)
+#define usleep(usec) Sleep((DWORD)((usec) / 1000))
+#else
+// Linux, Unix, Mac OS X
+#define yield() do { usleep(0); } while (0)
+#endif
+
+namespace GensSdl {
+
+static MdFb *crazyFb = nullptr;
+static CrazyEffect *crazyEffect = nullptr;
+
+/**
+ * Run the "Crazy Effect" loop.
+ * @return Exit code.
+ */
+int CrazyEffectLoop(void)
+{
+       // TODO: Move common code back to gens-sdl?
+
+       // Initialize the SDL handlers.
+       sdlHandler = new SdlHandler();
+       if (sdlHandler->init_video() < 0)
+               return EXIT_FAILURE;
+       // No audio here.
+       //if (sdlHandler->init_audio() < 0)
+       //      return EXIT_FAILURE;
+       vBackend = sdlHandler->vBackend();
+
+       // Set the window title.
+       sdlHandler->set_window_title("Gens/GS II [SDL]");
+
+       // Check for startup messages.
+       checkForStartupMessages();
+
+       // Start the frame timer.
+       // TODO: Region code?
+       clks.reset();
+
+       // Enable frameskip.
+       frameskip = true;
+
+       // Create the "Crazy" Effect framebuffer.
+       // Image size defaults to the full framebuffer,
+       // so we don't have to worry about "stretch modes".
+       crazyFb = new MdFb();
+       crazyFb->setBpp(MdFb::BPP_32);
+       // Set the SDL video source.
+       sdlHandler->set_video_source(crazyFb);
+
+       // Create the "Crazy" Effect object.
+       crazyEffect = new CrazyEffect();
+       crazyEffect->setColorMask(CrazyEffect::CM_WHITE);
+
+       // TODO: Move some more common stuff back to gens-sdl.cpp.
+       while (running) {
+               SDL_Event event;
+               int ret;
+               if (paused.data) {
+                       // Emulation is paused.
+                       if (!vBackend->has_osd_messages()) {
+                               // No OSD messages.
+                               // Wait for an SDL event.
+                               ret = SDL_WaitEvent(&event);
+                               if (ret) {
+                                       processSdlEvent_common(&event);
+                               }
+                       }
+
+                       // Process OSD messages.
+                       vBackend->process_osd_messages();
+               }
+               if (!running)
+                       break;
+
+               // Poll for SDL events, and wait for the queue
+               // to empty. This ensures that we don't end up
+               // only processing one event per frame.
+               do {
+                       ret = SDL_PollEvent(&event);
+                       if (ret) {
+                               processSdlEvent_common(&event);
+                       }
+               } while (running && ret != 0);
+               if (!running)
+                       break;
+
+               if (paused.data) {
+                       // Emulation is paused.
+                       // Only update video if the VBackend is dirty
+                       // or the SDL window has been exposed.
+                       sdlHandler->update_video_paused(exposed);
+
+                       // Don't run any frames.
+                       continue;
+               }
+
+               // Clear the 'exposed' flag.
+               exposed = false;
+
+               // New start time.
+               clks.new_clk = timing.getTime();
+
+               // Update the FPS counter.
+               unsigned int fps_tmp = ((clks.new_clk - clks.fps_clk) & 0x3FFFFF);
+               if (fps_tmp >= 1000000) {
+                       // More than 1 second has passed.
+                       clks.fps_clk = clks.new_clk;
+                       // FIXME: Just use abs() here.
+                       if (clks.frames_old > clks.frames) {
+                               clks.fps = (clks.frames_old - clks.frames);
+                       } else {
+                               clks.fps = (clks.frames - clks.frames_old);
+                       }
+                       clks.frames_old = clks.frames;
+
+                       // Update the window title.
+                       // TODO: Average the FPS over multiple seconds
+                       // and/or quarter-seconds.
+                       char win_title[256];
+                       snprintf(win_title, sizeof(win_title), "Gens/GS II [SDL] - %u fps", clks.fps);
+                       sdlHandler->set_window_title(win_title);
+               }
+
+               // Run the "Crazy" effect.
+               // TODO: Use the frameskip code to limit frames?
+               crazyEffect->run(crazyFb);
+               sdlHandler->update_video();
+               clks.frames++;
+               yield();
+               continue;
+       }
+
+       // Delete/unreference the "Crazy" Effect objects.
+       crazyFb->unref();
+       delete crazyEffect;
+
+       // Done running the "Crazy" Effect loop.
+       return 0;
+}
+
+}
diff --git a/src/gens-sdl/CrazyEffectLoop.hpp b/src/gens-sdl/CrazyEffectLoop.hpp
new file mode 100644 (file)
index 0000000..8a0c5e4
--- /dev/null
@@ -0,0 +1,35 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * CrazyEffectLoop.hpp: "Crazy" Effect loop.                               *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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 the   *
+ * Free Software Foundation; either version 2 of the License, or (at your  *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of              *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+#ifndef __GENS_SDL_CRAZYEFFECTLOOP_HPP__
+#define __GENS_SDL_CRAZYEFFECTLOOP_HPP__
+
+namespace GensSdl {
+
+/**
+ * Run the "Crazy Effect" loop.
+ * @return Exit code.
+ */
+int CrazyEffectLoop(void);
+
+}
+
+#endif /* __GENS_SDL_CRAZYEFFECTLOOP_HPP__ */
diff --git a/src/gens-sdl/EmuLoop.cpp b/src/gens-sdl/EmuLoop.cpp
new file mode 100644 (file)
index 0000000..55c866b
--- /dev/null
@@ -0,0 +1,715 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * EmuLoop.hpp: Main emulation loop.                                       *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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 the   *
+ * Free Software Foundation; either version 2 of the License, or (at your  *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of              *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+// Reentrant functions.
+// MUST be included before everything else due to
+// _POSIX_SOURCE and _POSIX_C_SOURCE definitions.
+#include "libcompat/reentrant.h"
+
+#include "EmuLoop.hpp"
+#include "gens-sdl.hpp"
+
+// Configuration.
+#include "Config.hpp"
+
+#include "SdlHandler.hpp"
+#include "VBackend.hpp"
+using GensSdl::SdlHandler;
+using GensSdl::VBackend;
+
+// String lookup for ROM information.
+#include "str_lookup.hpp"
+
+// LibGens
+#include "libgens/Rom.hpp"
+#include "libgens/Util/MdFb.hpp"
+using LibGens::Rom;
+using LibGens::MdFb;
+
+// Emulation Context.
+#include "libgens/EmuContext/EmuContext.hpp"
+#include "libgens/EmuContext/EmuContextFactory.hpp"
+using LibGens::EmuContext;
+using LibGens::EmuContextFactory;
+
+// LibGensKeys
+#include "libgens/IO/IoManager.hpp"
+#include "libgens/macros/common.h"
+#include "libgenskeys/KeyManager.hpp"
+#include "libgenskeys/GensKey_t.h"
+using LibGens::IoManager;
+using LibGensKeys::KeyManager;
+
+// LibZomg
+#include "libzomg/Zomg.hpp"
+#include "libzomg/img_data.h"
+using LibZomg::ZomgBase;
+using LibZomg::Zomg;
+
+// OS-specific includes.
+#ifdef _WIN32
+// Windows
+#include <windows.h>
+// Win32 Unicode Translation Layer.
+// Needed for proper Unicode filename support on Windows.
+#include "libcompat/W32U/W32U_mini.h"
+#include "libcompat/W32U/W32U_argv.h"
+#else
+// Linux, Unix, Mac OS X
+#include <unistd.h>
+#endif
+
+// yield(), aka usleep(0) or Sleep(0)
+#ifdef _WIN32
+// Windows
+#define yield() do { Sleep(0); } while (0)
+#define usleep(usec) Sleep((DWORD)((usec) / 1000))
+#else
+// Linux, Unix, Mac OS X
+#define yield() do { usleep(0); } while (0)
+#endif
+
+// C++ includes.
+#include <string>
+using std::string;
+
+namespace GensSdl {
+
+static Rom *rom = nullptr;
+static EmuContext *context = nullptr;
+static bool isPico = false;
+
+static KeyManager *keyManager = nullptr;
+// MD 6-button keyMap.
+static const GensKey_t keyMap_md[] = {
+       KEYV_UP, KEYV_DOWN, KEYV_LEFT, KEYV_RIGHT,      // UDLR
+       KEYV_s, KEYV_d, KEYV_a, KEYV_RETURN,            // BCAS
+       KEYV_e, KEYV_w, KEYV_q, KEYV_RSHIFT             // ZYXM
+};
+// Sega Pico keyMap.
+static const GensKey_t keyMap_pico[] = {
+       KEYV_UP, KEYV_DOWN, KEYV_LEFT, KEYV_RIGHT,              // UDLR
+       KEYV_SPACE, KEYV_PAGEDOWN, KEYV_PAGEUP, KEYV_RETURN     // BCAS
+       , 0, 0, 0, 0
+};
+
+// Save slot.
+static int saveSlot_selected = 0;
+
+/**
+ * Get the modification time string for the specified save file.
+ * @param zomg Save file.
+ * @return String contianing the mtime, or an error message if invalid.
+ */
+static string getSaveSlot_mtime(const ZomgBase *zomg)
+{
+       // TODO: This function can probably be optimized more...
+
+       // Slot state.
+       char slot_state[48];
+
+       // Check the mtime.
+       // TODO: mtime=0 is invalid.
+       bool doFullTimestamp = false;
+       time_t cur_time = time(nullptr);
+       time_t zomg_mtime = zomg->mtime();
+
+       // zomg_mtime is needed for printing.
+       // TODO: Custom localtime_r() if system version isn't available?
+       struct tm tm_zomg_mtime;
+       if (!localtime_r(&zomg_mtime, &tm_zomg_mtime)) {
+               // Error converting zomg_mtime.
+               return "occupied";
+       }
+
+       if (zomg_mtime > cur_time) {
+               // Savestate was modified in teh future!!1!
+               // Either that, or localtime_r() failed.
+               doFullTimestamp = true;
+               goto convert;
+       }
+
+       // Check if the times are "close enough" to omit the date.
+       struct tm tm_cur_time;
+       if (!localtime_r(&cur_time, &tm_cur_time)) {
+               // Error converting cur_time.
+               doFullTimestamp = true;
+               goto convert;
+       }
+
+       // Check if the times are within the same day.
+       if (tm_cur_time.tm_yday != tm_zomg_mtime.tm_yday) {
+               // Not the same day.
+               // Are the times within 12 hours?
+               if (cur_time - zomg_mtime >= (3600*12)) {
+                       // More than 12 hours.
+                       // Show the full date.
+                       doFullTimestamp = true;
+               }
+       }
+
+convert:
+       if (doFullTimestamp) {
+               // Show the full timestamp.
+               strftime(slot_state, sizeof(slot_state), "%x %X", &tm_zomg_mtime);
+       } else {
+               // Show only the time.
+               strftime(slot_state, sizeof(slot_state), "%X", &tm_zomg_mtime);
+       }
+       return string(slot_state);
+}
+
+/**
+ * Save slot selection.
+ * @param saveSlot Save slot. (0-9)
+ */
+static void doSaveSlot(int saveSlot)
+{
+       assert(saveSlot >= 0 && saveSlot <= 9);
+       if (saveSlot < 0 || saveSlot > 9)
+               return;
+       saveSlot_selected = saveSlot;
+
+       // Metadata variables.
+       string slot_state;
+       Zomg_Img_Data_t img_data;
+       img_data.data = nullptr;
+
+       // Check if the specified savestate exists.
+       // TODO: R_OK or just F_OK?
+       string filename = getSavestateFilename(rom, saveSlot);
+       if (!access(filename.c_str(), F_OK)) {
+               // Savestate exists.
+               // Load some file information.
+               LibZomg::Zomg zomg(filename.c_str(), Zomg::ZOMG_LOAD);
+               if (!zomg.isOpen()) {
+                       // Error opening the savestate.
+                       slot_state = "error";
+               } else {
+                       // Get the slot mtime.
+                       slot_state = getSaveSlot_mtime(&zomg);
+
+                       // Get the preview image.
+                       // TODO: Rename loadPreview() to loadPreviewImage()?
+                       int ret = zomg.loadPreview(&img_data);
+                       if (ret != 0) {
+                               // Image load failed.
+                               // TODO: Ensure all reading functions
+                               // called by loadPreview() free the
+                               // memory on error.
+                               img_data.data = nullptr;
+                       }
+               }
+       } else {
+               // Savestate does not exist.
+               slot_state = "empty";
+       }
+
+       // Show an OSD message.
+       vBackend->osd_printf(1500, "Slot %d [%s]", saveSlot, slot_state.c_str());
+       // If img_data.data is nullptr, this will hide the current image.
+       vBackend->osd_preview_image(1500, &img_data);
+       free(img_data.data);
+}
+
+/**
+ * Load the state in the selected slot.
+ */
+static void doLoadState(void)
+{
+       assert(saveSlot_selected >= 0 && saveSlot_selected <= 9);
+       if (saveSlot_selected < 0 || saveSlot_selected > 9)
+               return;
+
+       string filename = getSavestateFilename(rom, saveSlot_selected);
+       int ret = context->zomgLoad(filename.c_str());
+       if (ret == 0) {
+               // State loaded.
+               vBackend->osd_printf(1500, "Slot %d loaded.", saveSlot_selected);
+       } else {
+               // Error loading state.
+               if (ret == -ENOENT) {
+                       // File not found.
+                       vBackend->osd_printf(1500, "Slot %d is empty.", saveSlot_selected);
+               } else {
+                       // Other error.
+                       vBackend->osd_printf(1500,
+                               "Error loading Slot %d:\n* %s",
+                               saveSlot_selected, strerror(-ret));
+               }
+       }
+}
+
+/**
+ * Save the state in the selected slot.
+ */
+static void doSaveState(void)
+{
+       assert(saveSlot_selected >= 0 && saveSlot_selected <= 9);
+       if (saveSlot_selected < 0 || saveSlot_selected > 9)
+               return;
+
+       string filename = getSavestateFilename(rom, saveSlot_selected);
+       int ret = context->zomgSave(filename.c_str());
+       if (ret == 0) {
+               // State saved.
+               vBackend->osd_printf(1500, "Slot %d saved.", saveSlot_selected);
+       } else {
+               // Error saving state.
+               vBackend->osd_printf(1500,
+                               "Error saving Slot %d:\n* %s",
+                               saveSlot_selected, strerror(-ret));
+       }
+}
+
+/**
+ * Change stretch mode parameters.
+ */
+static void doStretchMode(void)
+{
+       // Change stretch mode parameters.
+       int stretchMode = (int)sdlHandler->vBackend()->stretchMode();
+       stretchMode++;
+       stretchMode &= 3;
+       sdlHandler->vBackend()->setStretchMode((VBackend::StretchMode_t)stretchMode);
+
+       // Show an OSD message.
+       const char *stretch;
+       switch (stretchMode) {
+               case VBackend::STRETCH_NONE:
+               default:
+                       stretch = "None";
+                       break;
+               case VBackend::STRETCH_H:
+                       stretch = "Horizontal";
+                       break;
+               case VBackend::STRETCH_V:
+                       stretch = "Vertical";
+                       break;
+               case VBackend::STRETCH_FULL:
+                       stretch = "Full";
+                       break;
+       }
+
+       vBackend->osd_printf(1500, "Stretch Mode set to %s.", stretch);
+}
+
+/**
+ * Take a screenshot.
+ */
+static void doScreenShot(void)
+{
+       int ret = GensSdl::doScreenShot(context->m_vdp->MD_Screen, rom);
+       if (ret >= 0) {
+               vBackend->osd_printf(1500, "Screenshot %d saved.", ret);
+       } else {
+               vBackend->osd_printf(1500, "Error saving screenshot:\n* %s", strerror(-ret));
+       }
+}
+
+/**
+ * Process an SDL event.
+ * EmuLoop-specific version; delegates to gens-sdl
+ * if the event isn't handled.
+ * @param event SDL event.
+ * @return 0 if the event was handled; non-zero if it wasn't.
+ */
+static int processSdlEvent_emuLoop(const SDL_Event *event) {
+       int ret = 0;
+       switch (event->type) {
+               case SDL_KEYDOWN:
+                       // SDL keycodes nearly match GensKey.
+                       // TODO: Split out into a separate function?
+                       // TODO: Check for "no modifiers" for some keys?
+                       switch (event->key.keysym.sym) {
+                               case SDLK_TAB:
+                                       // Check for Shift.
+                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
+                                               // Hard Reset.
+                                               context->hardReset();
+                                               vBackend->osd_print(1500, "Hard Reset.");
+                                       } else {
+                                               // Soft Reset.
+                                               context->softReset();
+                                               vBackend->osd_print(1500, "Soft Reset.");
+                                       }
+                                       break;
+
+                               case SDLK_BACKSPACE:
+                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
+                                               // Take a screenshot.
+                                               doScreenShot();
+                                       }
+                                       break;
+
+                               case SDLK_F2:
+                                       // NOTE: This is handled here instead of in the generic
+                                       // processSdlEvent_common() because stretch functionality
+                                       // isn't used in CrazyEffectLoop.
+                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
+                                               // Change stretch mode parameters.
+                                               doStretchMode();
+                                       } else {
+                                               // Not handling this event.
+                                               ret = 1;
+                                       }
+                                       break;
+
+                               case SDLK_0: case SDLK_1:
+                               case SDLK_2: case SDLK_3:
+                               case SDLK_4: case SDLK_5:
+                               case SDLK_6: case SDLK_7:
+                               case SDLK_8: case SDLK_9:
+                                       // Save slot selection.
+                                       doSaveSlot(event->key.keysym.sym - SDLK_0);
+                                       break;
+
+                               case SDLK_F5:
+                                       // Save state.
+                                       doSaveState();
+                                       break;
+
+                               case SDLK_F6: {
+                                       // Previous save slot.
+                                       int saveSlot  = ((saveSlot_selected + 9) % 10);
+                                       doSaveSlot(saveSlot);
+                                       break;
+                               }
+
+                               case SDLK_F7: {
+                                       // Next save slot.
+                                       int saveSlot  = ((saveSlot_selected + 1) % 10);
+                                       doSaveSlot(saveSlot);
+                                       break;
+                               }
+
+                               case SDLK_F8:
+                                       // Load state.
+                                       doLoadState();
+                                       break;
+
+                               default: {
+                                       // Check if the common event handler will handle this.
+                                       int ret = processSdlEvent_common(event);
+                                       if (ret != 0) {
+                                               // Not handled.
+                                               // Send the key to the KeyManager.
+                                               keyManager->keyDown(SdlHandler::scancodeToGensKey(event->key.keysym.scancode));
+                                               break;
+                                       }
+                                       break;
+                               }
+                       }
+                       break;
+
+               case SDL_KEYUP:
+                       // SDL keycodes nearly match GensKey.
+                       keyManager->keyUp(SdlHandler::scancodeToGensKey(event->key.keysym.scancode));
+                       break;
+
+               default:
+                       // Event not handled.
+                       ret = 1;
+                       break;
+       }
+
+       if (ret != 0) {
+               // Event wasn't handled.
+               // Try processSdlEvent_common().
+               ret = processSdlEvent_common(event);
+       }
+       return ret;
+}
+
+/**
+ * Run the emulation loop.
+ * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+ * @return Exit code.
+ */
+int EmuLoop(const char *rom_filename)
+{
+       // Load the ROM image.
+       rom = new Rom(rom_filename);
+       if (!rom->isOpen()) {
+               // Error opening the ROM.
+               // TODO: Error code?
+               fprintf(stderr, "Error opening ROM file %s: (TODO get error code)\n",
+                       rom_filename);
+               return EXIT_FAILURE;
+       }
+       if (rom->isMultiFile()) {
+               // Select the first file.
+               rom->select_z_entry(rom->get_z_entry_list());
+       }
+
+       // Is the ROM format supported?
+       if (!EmuContextFactory::isRomFormatSupported(rom)) {
+               // ROM format is not supported.
+               const char *rom_format = romFormatToString(rom->romFormat());
+               fprintf(stderr, "Error loading ROM file %s: ROM is in %s format.\nOnly plain binary and SMD-format ROMs are supported.\n",
+                       rom_filename, rom_format);
+               return EXIT_FAILURE;
+       }
+
+       // Check the ROM's system ID.
+       if (!EmuContextFactory::isRomSystemSupported(rom)) {
+               // System is not supported.
+               const char *rom_sysId = sysIdToString(rom->sysId());
+               fprintf(stderr, "Error loading ROM file %s: ROM is for %s.\nOnly Mega Drive and Pico ROMs are supported.\n",
+                       rom_filename, rom_sysId);
+               return EXIT_FAILURE;
+       }
+
+       // Check for Pico controller.
+       isPico = false;
+       if (rom->sysId() == Rom::MDP_SYSTEM_PICO) {
+               isPico = true;
+       }
+
+       // Set the SRAM/EEPROM path.
+       EmuContext::SetPathSRam(getConfigDir("SRAM").c_str());
+
+       // Create the emulation context.
+       context = EmuContextFactory::createContext(rom);
+       if (!context || !context->isRomOpened()) {
+               // Error loading the ROM into EmuMD.
+               // TODO: Error code?
+               fprintf(stderr, "Error initializing EmuContext for %s: (TODO get error code)\n",
+                       rom_filename);
+               return EXIT_FAILURE;
+       }
+
+       // Initialize the SDL handlers.
+       sdlHandler = new SdlHandler();
+       if (sdlHandler->init_video() < 0)
+               return EXIT_FAILURE;
+       if (sdlHandler->init_audio() < 0)
+               return EXIT_FAILURE;
+       vBackend = sdlHandler->vBackend();
+
+       // Set the window title.
+       sdlHandler->set_window_title("Gens/GS II [SDL]");
+
+       // Check for startup messages.
+       checkForStartupMessages();
+
+       // Start the frame timer.
+       // TODO: Region code?
+       bool isPal = false;
+       const unsigned int usec_per_frame = (1000000 / (isPal ? 50 : 60));
+       clks.reset();
+
+       // Enable frameskip.
+       frameskip = true;
+
+       // TODO: Close the ROM, or let EmuContext do it?
+
+       // Set the color depth.
+       // TODO: Command line option?
+       MdFb *fb = context->m_vdp->MD_Screen->ref();
+       fb->setBpp(MdFb::BPP_32);
+
+       // Set the SDL video source.
+       sdlHandler->set_video_source(fb);
+
+       // Start audio.
+       sdlHandler->pause_audio(false);
+
+       // Initialize the I/O Manager with a default key layout.
+       keyManager = new KeyManager();
+       if (!isPico) {
+               // Standard Mega Drive controllers.
+               keyManager->setIoType(IoManager::VIRTPORT_1, IoManager::IOT_6BTN);
+               keyManager->setKeyMap(IoManager::VIRTPORT_1, keyMap_md, ARRAY_SIZE(keyMap_md));
+               keyManager->setIoType(IoManager::VIRTPORT_2, IoManager::IOT_NONE);
+       } else {
+               // Sega Pico controller.
+               keyManager->setIoType(IoManager::VIRTPORT_1, IoManager::IOT_PICO);
+               keyManager->setKeyMap(IoManager::VIRTPORT_1, keyMap_pico, ARRAY_SIZE(keyMap_pico));
+               keyManager->setIoType(IoManager::VIRTPORT_2, IoManager::IOT_NONE);
+       }
+
+       // TODO: Move some more common stuff back to gens-sdl.cpp.
+       uint8_t old_paused = 0;
+       while (running) {
+               SDL_Event event;
+               int ret;
+               if (paused.data) {
+                       // Emulation is paused.
+                       if (!vBackend->has_osd_messages()) {
+                               // No OSD messages.
+                               // Wait for an SDL event.
+                               ret = SDL_WaitEvent(&event);
+                               if (ret) {
+                                       processSdlEvent_emuLoop(&event);
+                               }
+                       }
+
+                       // Process OSD messages.
+                       vBackend->process_osd_messages();
+               }
+               if (!running)
+                       break;
+
+               // Poll for SDL events, and wait for the queue
+               // to empty. This ensures that we don't end up
+               // only processing one event per frame.
+               do {
+                       ret = SDL_PollEvent(&event);
+                       if (ret) {
+                               processSdlEvent_emuLoop(&event);
+                       }
+               } while (running && ret != 0);
+               if (!running)
+                       break;
+
+               // Check if the 'paused' state was changed.
+               // If it was, autosave SRAM/EEPROM.
+               if (old_paused != paused.data) {
+                       // 'paused' state was changed.
+                       // (TODO: Only if paused == true?)
+                       context->autoSaveData(-1);
+                       old_paused = paused.data;
+               }
+
+               if (paused.data) {
+                       // Emulation is paused.
+                       // Only update video if the VBackend is dirty
+                       // or the SDL window has been exposed.
+                       sdlHandler->update_video_paused(exposed);
+
+                       // Don't run any frames.
+                       continue;
+               }
+
+               // Clear the 'exposed' flag.
+               exposed = false;
+
+               // New start time.
+               clks.new_clk = timing.getTime();
+
+               // Update the FPS counter.
+               unsigned int fps_tmp = ((clks.new_clk - clks.fps_clk) & 0x3FFFFF);
+               if (fps_tmp >= 1000000) {
+                       // More than 1 second has passed.
+                       clks.fps_clk = clks.new_clk;
+                       // FIXME: Just use abs() here.
+                       if (clks.frames_old > clks.frames) {
+                               clks.fps = (clks.frames_old - clks.frames);
+                       } else {
+                               clks.fps = (clks.frames - clks.frames_old);
+                       }
+                       clks.frames_old = clks.frames;
+
+                       // Update the window title.
+                       // TODO: Average the FPS over multiple seconds
+                       // and/or quarter-seconds.
+                       char win_title[256];
+                       snprintf(win_title, sizeof(win_title), "Gens/GS II [SDL] - %u fps", clks.fps);
+                       sdlHandler->set_window_title(win_title);
+               }
+
+               // Frameskip.
+               if (frameskip) {
+                       // Determine how many frames to run.
+                       clks.usec_frameskip += ((clks.new_clk - clks.old_clk) & 0x3FFFFF); // no more than 4 secs
+                       unsigned int frames_todo = (unsigned int)(clks.usec_frameskip / usec_per_frame);
+                       clks.usec_frameskip %= usec_per_frame;
+                       clks.old_clk = clks.new_clk;
+
+                       if (frames_todo == 0) {
+                               // No frames to do yet.
+                               // Wait until the next frame.
+                               uint64_t usec_sleep = (usec_per_frame - clks.usec_frameskip);
+                               if (usec_sleep > 1000) {
+                                       // Never sleep for longer than the 50 Hz value
+                                       // so events are checked often enough.
+                                       if (usec_sleep > (1000000 / 50)) {
+                                               usec_sleep = (1000000 / 50);
+                                       }
+                                       usec_sleep -= 1000;
+
+#ifdef _WIN32
+                                       // Win32: Use a yield() loop.
+                                       // FIXME: Doesn't work properly on VBox/WinXP...
+                                       uint64_t yield_end = timing.getTime() + usec_sleep;
+                                       do {
+                                               yield();
+                                       } while (yield_end > timing.getTime());
+#else /* !_WIN32 */
+                                       // Linux: Use usleep().
+                                       usleep(usec_sleep);
+#endif /* _WIN32 */
+                               }
+                       } else {
+                               // Draw frames.
+                               for (; frames_todo != 1; frames_todo--) {
+                                       // Run a frame without rendering.
+                                       context->execFrameFast();
+                                       sdlHandler->update_audio();
+                               }
+                               frames_todo = 0;
+
+                               // Run a frame and render it.
+                               context->execFrame();
+                               sdlHandler->update_audio();
+                               sdlHandler->update_video();
+                               // Increment the frame counter.
+                               clks.frames++;
+
+                               // Autosave SRAM/EEPROM.
+                               // TODO: EmuContext::execFrame() should probably do this itself...
+                               context->autoSaveData(1);
+                       }
+               } else {
+                       // Run a frame and render it.
+                       context->execFrame();
+                       sdlHandler->update_audio();
+                       sdlHandler->update_video();
+                       // Increment the frame counter.
+                       clks.frames++;
+
+                       // Autosave SRAM/EEPROM.
+                       // TODO: EmuContext::execFrame() should probably do this itself...
+                       context->autoSaveData(1);
+               }
+
+               // Update the I/O manager.
+               keyManager->updateIoManager(context->m_ioManager);
+       }
+
+       // Unreference the framebuffer.
+       fb->unref();
+
+       // Save SRAM/EEPROM, if necessary.
+       // TODO: Move to EmuContext::~EmuContext()?
+       context->saveData();
+
+       // Shut down LibGens.
+       delete keyManager;
+       delete context;
+       delete rom;
+
+       // Done running the emulation loop.
+       return 0;
+}
+
+}
diff --git a/src/gens-sdl/EmuLoop.hpp b/src/gens-sdl/EmuLoop.hpp
new file mode 100644 (file)
index 0000000..35e65d0
--- /dev/null
@@ -0,0 +1,38 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * EmuLoop.hpp: Main emulation loop.                                       *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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 the   *
+ * Free Software Foundation; either version 2 of the License, or (at your  *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of              *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+#ifndef __GENS_SDL_EMULOOP_HPP__
+#define __GENS_SDL_EMULOOP_HPP__
+
+namespace GensSdl {
+
+// TODO: Make a base "main loop" class?
+
+/**
+ * Run the emulation loop.
+ * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+ * @return Exit code.
+ */
+int EmuLoop(const char *rom_filename);
+
+}
+
+#endif /* __GENS_SDL_EMULOOP_HPP__ */
index aff774c..34ed54e 100644 (file)
@@ -1,6 +1,6 @@
 /***************************************************************************
  * gens-sdl: Gens/GS II basic SDL frontend.                                *
- * gens-sdl.cpp: Entry point and main event loop.                          *
+ * gens-sdl.hpp: Entry point.                                              *
  *                                                                         *
  * Copyright (c) 2015 by David Korth.                                      *
  *                                                                         *
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
  ***************************************************************************/
 
+// TODO BEFORE COMMIT: Remove any more unnecessary includes.
+// Also, after commit, convert emulation loops into classes?
+
 // Reentrant functions.
 // MUST be included before everything else due to
 // _POSIX_SOURCE and _POSIX_C_SOURCE definitions.
 #include "libcompat/reentrant.h"
 
 #include <config.gens-sdl.h>
+#include "gens-sdl.hpp"
+#include <SDL.h>
 
-#include "SdlHandler.hpp"
+// Configuration.
 #include "Config.hpp"
+
+#include "SdlHandler.hpp"
 #include "VBackend.hpp"
 using GensSdl::SdlHandler;
 using GensSdl::VBackend;
 
-#include "str_lookup.hpp"
-
 // LibGens
 #include "libgens/lg_main.hpp"
 #include "libgens/lg_osd.h"
-#include "libgens/Rom.hpp"
 #include "libgens/Util/MdFb.hpp"
-#include "libgens/Util/Timing.hpp"
-using LibGens::Rom;
 using LibGens::MdFb;
 using LibGens::Timing;
 
-// "Crazy" Effect.
-#include "libgens/Effects/CrazyEffect.hpp"
-using LibGens::CrazyEffect;
-
-// Emulation Context.
-#include "libgens/EmuContext/EmuContext.hpp"
-#include "libgens/EmuContext/EmuContextFactory.hpp"
-using LibGens::EmuContext;
-using LibGens::EmuContextFactory;
-
-// LibGensKeys
-#include "libgens/IO/IoManager.hpp"
-#include "libgens/macros/common.h"
-#include "libgenskeys/KeyManager.hpp"
-#include "libgenskeys/GensKey_t.h"
-using LibGens::IoManager;
-using LibGensKeys::KeyManager;
-
-// LibZomg
-#include "libzomg/Zomg.hpp"
-#include "libzomg/img_data.h"
-using LibZomg::ZomgBase;
-using LibZomg::Zomg;
+// Main event loops.
+#include "EmuLoop.hpp"
+#include "CrazyEffectLoop.hpp"
 
 // OS-specific includes.
 #ifdef _WIN32
@@ -103,15 +85,10 @@ using LibZomg::Zomg;
 using std::string;
 using std::vector;
 
-#include <SDL.h>
-
 namespace GensSdl {
 
-static SdlHandler *sdlHandler = nullptr;
-static VBackend *vBackend = nullptr;
-static Rom *rom = nullptr;
-static EmuContext *context = nullptr;
-static bool isPico = false;
+SdlHandler *sdlHandler = nullptr;
+VBackend *vBackend = nullptr;
 
 /** Command line parameters. **/
 // TODO: Write a popt-based command line parser with
@@ -120,22 +97,6 @@ static const char *rom_filename = nullptr;
 static bool autoPause = false;
 // If true, don't emulate anything; just run the Crazy Effect.
 static bool runCrazyEffect = false;
-static MdFb *crazyFb = nullptr;
-static CrazyEffect *crazyEffect = nullptr;
-
-static KeyManager *keyManager = nullptr;
-// MD 6-button keyMap.
-static const GensKey_t keyMap_md[] = {
-       KEYV_UP, KEYV_DOWN, KEYV_LEFT, KEYV_RIGHT,      // UDLR
-       KEYV_s, KEYV_d, KEYV_a, KEYV_RETURN,            // BCAS
-       KEYV_e, KEYV_w, KEYV_q, KEYV_RSHIFT             // ZYXM
-};
-// Sega Pico keyMap.
-static const GensKey_t keyMap_pico[] = {
-       KEYV_UP, KEYV_DOWN, KEYV_LEFT, KEYV_RIGHT,              // UDLR
-       KEYV_SPACE, KEYV_PAGEDOWN, KEYV_PAGEUP, KEYV_RETURN     // BCAS
-       , 0, 0, 0, 0
-};
 
 // Startup OSD message queue.
 struct OsdStartup {
@@ -210,225 +171,37 @@ static void gsdl_osd(OsdType osd_type, int param)
        }
 }
 
-static LibGens::Timing timing;
-
-// Emulation state.
-static bool running = true;
-static union {
-       struct {
-               uint8_t manual  : 1;    // Manual pause.
-               uint8_t focus   : 1;    // Auto pause when focus is lost.
-       };
-       uint8_t data;
-} paused;
-
-// Enable frameskip.
-static bool frameskip = true;
-
-// Window has been exposed.
-// Video should be updated if emulation is paused.
-static bool exposed = false;
-
-// Frameskip timers.
-class clks_t {
-       public:
-               // Reset frameskip timers.
-               void reset(void) {
-                       start_clk = timing.getTime();
-                       old_clk = start_clk;
-                       fps_clk = start_clk;
-                       fps_clk = start_clk;
-                       new_clk = start_clk;
-                       usec_frameskip = 0;
-
-                       // Frame counter.
-                       frames = 0;
-                       frames_old = 0;
-                       fps = 0;
-               }
-
-               uint64_t start_clk;
-               uint64_t old_clk;
-               uint64_t fps_clk;
-               uint64_t new_clk;
-               // Microsecond counter for frameskip.
-               uint64_t usec_frameskip;
-
-               // Frame counters.
-               unsigned int frames;
-               unsigned int frames_old;
-               unsigned int fps;       // TODO: float or double?
-};
-static clks_t clks;
-
-// Save slot.
-static int saveSlot_selected = 0;
-
 /**
- * Get the modification time string for the specified save file.
- * @param zomg Save file.
- * @return String contianing the mtime, or an error message if invalid.
+ * Check for any OSD messages that were printed during startup.
+ * These messages would have been printed before VBackend
+ * was initialized, so they had to be temporarily stored.
  */
-static string getSaveSlot_mtime(const ZomgBase *zomg)
+void checkForStartupMessages(void)
 {
-       // TODO: This function can probably be optimized more...
-
-       // Slot state.
-       char slot_state[48];
-
-       // Check the mtime.
-       // TODO: mtime=0 is invalid.
-       bool doFullTimestamp = false;
-       time_t cur_time = time(nullptr);
-       time_t zomg_mtime = zomg->mtime();
-
-       // zomg_mtime is needed for printing.
-       // TODO: Custom localtime_r() if system version isn't available?
-       struct tm tm_zomg_mtime;
-       if (!localtime_r(&zomg_mtime, &tm_zomg_mtime)) {
-               // Error converting zomg_mtime.
-               return "occupied";
-       }
-
-       if (zomg_mtime > cur_time) {
-               // Savestate was modified in teh future!!1!
-               // Either that, or localtime_r() failed.
-               doFullTimestamp = true;
-               goto convert;
-       }
-
-       // Check if the times are "close enough" to omit the date.
-       struct tm tm_cur_time;
-       if (!localtime_r(&cur_time, &tm_cur_time)) {
-               // Error converting cur_time.
-               doFullTimestamp = true;
-               goto convert;
-       }
-
-       // Check if the times are within the same day.
-       if (tm_cur_time.tm_yday != tm_zomg_mtime.tm_yday) {
-               // Not the same day.
-               // Are the times within 12 hours?
-               if (cur_time - zomg_mtime >= (3600*12)) {
-                       // More than 12 hours.
-                       // Show the full date.
-                       doFullTimestamp = true;
+       if (!startup_queue.empty()) {
+               for (int i = 0; i < (int)startup_queue.size(); i++) {
+                       const OsdStartup &startup = startup_queue.at(i);
+                       vBackend->osd_printf(startup.duration, startup.msg, startup.param);
                }
        }
-
-convert:
-       if (doFullTimestamp) {
-               // Show the full timestamp.
-               strftime(slot_state, sizeof(slot_state), "%x %X", &tm_zomg_mtime);
-       } else {
-               // Show only the time.
-               strftime(slot_state, sizeof(slot_state), "%X", &tm_zomg_mtime);
-       }
-       return string(slot_state);
 }
 
-/**
- * Save slot selection.
- * @param saveSlot Save slot. (0-9)
- */
-static void doSaveSlot(int saveSlot)
-{
-       assert(saveSlot >= 0 && saveSlot <= 9);
-       if (saveSlot < 0 || saveSlot > 9)
-               return;
-       saveSlot_selected = saveSlot;
-
-       // Metadata variables.
-       string slot_state;
-       Zomg_Img_Data_t img_data;
-       img_data.data = nullptr;
+// Timing object.
+LibGens::Timing timing;
 
-       // Check if the specified savestate exists.
-       // TODO: R_OK or just F_OK?
-       string filename = getSavestateFilename(rom, saveSlot);
-       if (!access(filename.c_str(), F_OK)) {
-               // Savestate exists.
-               // Load some file information.
-               LibZomg::Zomg zomg(filename.c_str(), Zomg::ZOMG_LOAD);
-               if (!zomg.isOpen()) {
-                       // Error opening the savestate.
-                       slot_state = "error";
-               } else {
-                       // Get the slot mtime.
-                       slot_state = getSaveSlot_mtime(&zomg);
-
-                       // Get the preview image.
-                       // TODO: Rename loadPreview() to loadPreviewImage()?
-                       int ret = zomg.loadPreview(&img_data);
-                       if (ret != 0) {
-                               // Image load failed.
-                               // TODO: Ensure all reading functions
-                               // called by loadPreview() free the
-                               // memory on error.
-                               img_data.data = nullptr;
-                       }
-               }
-       } else {
-               // Savestate does not exist.
-               slot_state = "empty";
-       }
-
-       // Show an OSD message.
-       vBackend->osd_printf(1500, "Slot %d [%s]", saveSlot, slot_state.c_str());
-       // If img_data.data is nullptr, this will hide the current image.
-       vBackend->osd_preview_image(1500, &img_data);
-       free(img_data.data);
-}
-
-/**
- * Load the state in the selected slot.
- */
-static void doLoadState(void)
-{
-       assert(saveSlot_selected >= 0 && saveSlot_selected <= 9);
-       if (saveSlot_selected < 0 || saveSlot_selected > 9)
-               return;
+// Emulation state.
+bool running = true;
+paused_t paused;
 
-       string filename = getSavestateFilename(rom, saveSlot_selected);
-       int ret = context->zomgLoad(filename.c_str());
-       if (ret == 0) {
-               // State loaded.
-               vBackend->osd_printf(1500, "Slot %d loaded.", saveSlot_selected);
-       } else {
-               // Error loading state.
-               if (ret == -ENOENT) {
-                       // File not found.
-                       vBackend->osd_printf(1500, "Slot %d is empty.", saveSlot_selected);
-               } else {
-                       // Other error.
-                       vBackend->osd_printf(1500,
-                               "Error loading Slot %d:\n* %s",
-                               saveSlot_selected, strerror(-ret));
-               }
-       }
-}
+// Enable frameskip.
+bool frameskip = true;
 
-/**
- * Save the state in the selected slot.
- */
-static void doSaveState(void)
-{
-       assert(saveSlot_selected >= 0 && saveSlot_selected <= 9);
-       if (saveSlot_selected < 0 || saveSlot_selected > 9)
-               return;
+// Window has been exposed.
+// Video should be updated if emulation is paused.
+bool exposed = false;
 
-       string filename = getSavestateFilename(rom, saveSlot_selected);
-       int ret = context->zomgSave(filename.c_str());
-       if (ret == 0) {
-               // State saved.
-               vBackend->osd_printf(1500, "Slot %d saved.", saveSlot_selected);
-       } else {
-               // Error saving state.
-               vBackend->osd_printf(1500,
-                               "Error saving Slot %d:\n* %s",
-                               saveSlot_selected, strerror(-ret));
-       }
-}
+// Frameskip timers.
+clks_t clks;
 
 /**
  * Toggle Fast Blur.
@@ -447,38 +220,6 @@ static void doFastBlur(void)
 }
 
 /**
- * Change stretch mode parameters.
- */
-static void doStretchMode(void)
-{
-       // Change stretch mode parameters.
-       int stretchMode = (int)sdlHandler->vBackend()->stretchMode();
-       stretchMode++;
-       stretchMode &= 3;
-       sdlHandler->vBackend()->setStretchMode((VBackend::StretchMode_t)stretchMode);
-
-       // Show an OSD message.
-       const char *stretch;
-       switch (stretchMode) {
-               case VBackend::STRETCH_NONE:
-               default:
-                       stretch = "None";
-                       break;
-               case VBackend::STRETCH_H:
-                       stretch = "Horizontal";
-                       break;
-               case VBackend::STRETCH_V:
-                       stretch = "Vertical";
-                       break;
-               case VBackend::STRETCH_FULL:
-                       stretch = "Full";
-                       break;
-       }
-
-       vBackend->osd_printf(1500, "Stretch Mode set to %s.", stretch);
-}
-
-/**
  * Common pause processing function.
  * Called by doPause() and doAutoPause().
  */
@@ -494,9 +235,6 @@ static void doPauseProcessing(void)
        clks.reset();
        // Pause audio.
        sdlHandler->pause_audio(any);
-       // Autosave SRAM/EEPROM.
-       // (TODO: Only if paused == true?)
-       context->autoSaveData(-1);
 
        // Update the window title.
        if (manual) {
@@ -526,19 +264,6 @@ static void doAutoPause(bool lostFocus)
 }
 
 /**
- * Take a screenshot.
- */
-static void doScreenShot(void)
-{
-       int ret = GensSdl::doScreenShot(context->m_vdp->MD_Screen, rom);
-       if (ret >= 0) {
-               vBackend->osd_printf(1500, "Screenshot %d saved.", ret);
-       } else {
-               vBackend->osd_printf(1500, "Error saving screenshot:\n* %s", strerror(-ret));
-       }
-}
-
-/**
  * Show the "About" message.
  */
 static void doAboutMessage(void)
@@ -577,50 +302,38 @@ static void doAboutMessage(void)
 
 /**
  * Process an SDL event.
+ * EmuLoop processes SDL events first; if it ends up
+ * with an event that it can't handle, it goes here.
  * @param event SDL event.
+ * @return 0 if the event was handled; non-zero if it wasn't.
  */
-static void processSdlEvent(const SDL_Event *event) {
+int processSdlEvent_common(const SDL_Event *event) {
+       // NOTE: Some keys are processed in specific event loops
+       // instead of here because they only apply if a ROM is loaded.
+       // gens-qt4 won't make a distinction, since it can run with
+       // both a ROM loaded and not loaded, and you can open and close
+       // ROMs without restarting the program, so it has to be able
+       // to switch modes while allowing options to be change both
+       // when a ROM is loaded and when no ROM is loaded.
+       // In essence, it combines both EmuLoop and CrazyEffectLoop
+       // while maintaining the state of various emulation options.
+
+       int ret = 0;
        switch (event->type) {
                case SDL_QUIT:
-                       running = 0;
+                       running = false;
                        break;
 
                case SDL_KEYDOWN:
                        // SDL keycodes nearly match GensKey.
                        // TODO: Split out into a separate function?
+                       // TODO: Check for "no modifiers" for some keys?
                        switch (event->key.keysym.sym) {
-                               case SDLK_TAB:
-                                       // Check for Shift.
-                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
-                                               // Hard Reset.
-                                               context->hardReset();
-                                               vBackend->osd_print(1500, "Hard Reset.");
-                                       } else {
-                                               // Soft Reset.
-                                               context->softReset();
-                                               vBackend->osd_print(1500, "Soft Reset.");
-                                       }
-                                       break;
-
                                case SDLK_ESCAPE:
                                        // Pause emulation.
                                        doPause();
                                        break;
 
-                               case SDLK_BACKSPACE:
-                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
-                                               // Take a screenshot.
-                                               doScreenShot();
-                                       }
-                                       break;
-
-                               case SDLK_F2:
-                                       if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
-                                               // Change stretch mode parameters.
-                                               doStretchMode();
-                                       }
-                                       break;
-
                                case SDLK_RETURN:
                                        // Check for Alt+Enter.
                                        if ((event->key.keysym.mod & KMOD_ALT) &&
@@ -630,49 +343,16 @@ static void processSdlEvent(const SDL_Event *event) {
                                                sdlHandler->toggle_fullscreen();
                                        } else {
                                                // Not Alt+Enter.
-                                               // Send the key to the KeyManager.
-                                               keyManager->keyDown(SdlHandler::scancodeToGensKey(event->key.keysym.scancode));
+                                               // We're not handling this event.
+                                               ret = 1;
                                        }
                                        break;
 
-                               case SDLK_0: case SDLK_1:
-                               case SDLK_2: case SDLK_3:
-                               case SDLK_4: case SDLK_5:
-                               case SDLK_6: case SDLK_7:
-                               case SDLK_8: case SDLK_9:
-                                       // Save slot selection.
-                                       doSaveSlot(event->key.keysym.sym - SDLK_0);
-                                       break;
-
                                case SDLK_F1:
                                        // Show the "About" message.
                                        doAboutMessage();
                                        break;
 
-                               case SDLK_F5:
-                                       // Save state.
-                                       doSaveState();
-                                       break;
-
-                               case SDLK_F6: {
-                                       // Previous save slot.
-                                       int saveSlot  = ((saveSlot_selected + 9) % 10);
-                                       doSaveSlot(saveSlot);
-                                       break;
-                               }
-
-                               case SDLK_F7: {
-                                       // Next save slot.
-                                       int saveSlot  = ((saveSlot_selected + 1) % 10);
-                                       doSaveSlot(saveSlot);
-                                       break;
-                               }
-
-                               case SDLK_F8:
-                                       // Load state.
-                                       doLoadState();
-                                       break;
-
                                case SDLK_F9:
                                        // Fast Blur.
                                        doFastBlur();
@@ -684,17 +364,12 @@ static void processSdlEvent(const SDL_Event *event) {
                                        break;
 
                                default:
-                                       // Send the key to the KeyManager.
-                                       keyManager->keyDown(SdlHandler::scancodeToGensKey(event->key.keysym.scancode));
+                                       // Event not handled.
+                                       ret = 1;
                                        break;
                        }
                        break;
 
-               case SDL_KEYUP:
-                       // SDL keycodes nearly match GensKey.
-                       keyManager->keyUp(SdlHandler::scancodeToGensKey(event->key.keysym.scancode));
-                       break;
-
                case SDL_WINDOWEVENT:
                        switch (event->window.event) {
                                case SDL_WINDOWEVENT_RESIZED:
@@ -719,12 +394,20 @@ static void processSdlEvent(const SDL_Event *event) {
                                                doAutoPause(false);
                                        }
                                        break;
+                               default:
+                                       // Event not handled.
+                                       ret = 1;
+                                       break;
                        }
                        break;
 
                default:
+                       // Event not handled.
+                       ret = 1;
                        break;
        }
+
+       return ret;
 }
 
 /**
@@ -745,311 +428,39 @@ int run(void)
        // Register the LibGens OSD handler.
        lg_set_osd_fn(gsdl_osd);
 
-       // Load the ROM image.
-       rom = new Rom(rom_filename);
-       if (!rom->isOpen()) {
-               // Error opening the ROM.
-               // TODO: Error code?
-               fprintf(stderr, "Error opening ROM file %s: (TODO get error code)\n",
-                       rom_filename);
-               return EXIT_FAILURE;
-       }
-       if (rom->isMultiFile()) {
-               // Select the first file.
-               rom->select_z_entry(rom->get_z_entry_list());
-       }
-
-       // Is the ROM format supported?
-       if (!EmuContextFactory::isRomFormatSupported(rom)) {
-               // ROM format is not supported.
-               const char *rom_format = romFormatToString(rom->romFormat());
-               fprintf(stderr, "Error loading ROM file %s: ROM is in %s format.\nOnly plain binary and SMD-format ROMs are supported.\n",
-                       rom_filename, rom_format);
-               return EXIT_FAILURE;
-       }
-
-       // Check the ROM's system ID.
-       if (!EmuContextFactory::isRomSystemSupported(rom)) {
-               // System is not supported.
-               const char *rom_sysId = sysIdToString(rom->sysId());
-               fprintf(stderr, "Error loading ROM file %s: ROM is for %s.\nOnly Mega Drive and Pico ROMs are supported.\n",
-                       rom_filename, rom_sysId);
-               return EXIT_FAILURE;
-       }
-
-       // Check for Pico controller.
-       isPico = false;
-       if (rom->sysId() == Rom::MDP_SYSTEM_PICO) {
-               isPico = true;
-       }
-
-       // Set the SRAM/EEPROM path.
-       EmuContext::SetPathSRam(getConfigDir("SRAM").c_str());
-
-       // Create the emulation context.
-       context = EmuContextFactory::createContext(rom);
-       if (!context || !context->isRomOpened()) {
-               // Error loading the ROM into EmuMD.
-               // TODO: Error code?
-               fprintf(stderr, "Error initializing EmuContext for %s: (TODO get error code)\n",
-                       rom_filename);
-               return EXIT_FAILURE;
-       }
-
-       // Initialize SDL handlers.
-       sdlHandler = new SdlHandler();
-       if (sdlHandler->init_video() < 0)
-               return EXIT_FAILURE;
-       if (sdlHandler->init_audio() < 0)
-               return EXIT_FAILURE;
-
-       // Get the Video Backend.
-       vBackend = sdlHandler->vBackend();
-
-       // Set the window title.
-       sdlHandler->set_window_title("Gens/GS II [SDL]");
-
-       // Check for startup messages.
-       if (!startup_queue.empty()) {
-               for (int i = 0; i < (int)startup_queue.size(); i++) {
-                       const OsdStartup &startup = startup_queue.at(i);
-                       vBackend->osd_printf(startup.duration, startup.msg, startup.param);
-               }
-       }
-
-       // Start the frame timer.
-       // TODO: Region code?
-       bool isPal = false;
-       const unsigned int usec_per_frame = (1000000 / (isPal ? 50 : 60));
-       clks.reset();
-
-       // Enable frameskip.
-       frameskip = true;
-
-       // TODO: Close the ROM, or let EmuContext do it?
-
-       // Set the color depth.
-       MdFb *fb = context->m_vdp->MD_Screen->ref();
-       fb->setBpp(MdFb::BPP_32);
-
-       // Set the SDL video source.
-       sdlHandler->set_video_source(fb);
-
-       // Start audio.
-       sdlHandler->pause_audio(false);
-
-       // Initialize the I/O Manager with a default key layout.
-       keyManager = new KeyManager();
-       if (!isPico) {
-               // Standard Mega Drive controllers.
-               keyManager->setIoType(IoManager::VIRTPORT_1, IoManager::IOT_6BTN);
-               keyManager->setKeyMap(IoManager::VIRTPORT_1, keyMap_md, ARRAY_SIZE(keyMap_md));
-               keyManager->setIoType(IoManager::VIRTPORT_2, IoManager::IOT_NONE);
-       } else {
-               // Sega Pico controller.
-               keyManager->setIoType(IoManager::VIRTPORT_1, IoManager::IOT_PICO);
-               keyManager->setKeyMap(IoManager::VIRTPORT_1, keyMap_pico, ARRAY_SIZE(keyMap_pico));
-               keyManager->setIoType(IoManager::VIRTPORT_2, IoManager::IOT_NONE);
-       }
-
+       int ret;
        if (runCrazyEffect) {
-               // Just run the Crazy Effect.
-               // TODO: If specified, run a completely different
-               // main loop that doesn't create an emulation context
-               // or initialize audio.
-               crazyFb = new MdFb();
-               crazyFb->setBpp(MdFb::BPP_32);
-               sdlHandler->set_video_source(crazyFb);
-
-               crazyEffect = new CrazyEffect();
-               crazyEffect->setColorMask(CrazyEffect::CM_WHITE);
+               // Run the Crazy Effect.
+               ret = CrazyEffectLoop();
+       } else {
+               // Start the emulation loop.
+               ret = EmuLoop(rom_filename);
        }
 
-       while (running) {
-               SDL_Event event;
-               int ret;
-               if (paused.data) {
-                       // Emulation is paused.
-                       if (!vBackend->has_osd_messages()) {
-                               // No OSD messages.
-                               // Wait for an SDL event.
-                               ret = SDL_WaitEvent(&event);
-                               if (ret) {
-                                       processSdlEvent(&event);
-                               }
-                       }
-
-                       // Process OSD messages.
-                       vBackend->process_osd_messages();
-               }
-               if (!running)
-                       break;
-
-               // Poll for SDL events, and wait for the queue
-               // to empty. This ensures that we don't end up
-               // only processing one event per frame.
-               do {
-                       ret = SDL_PollEvent(&event);
-                       if (ret) {
-                               processSdlEvent(&event);
-                       }
-               } while (running && ret != 0);
-               if (!running)
-                       break;
-
-               if (paused.data) {
-                       // Emulation is paused.
-                       // Only update video if the VBackend is dirty
-                       // or the SDL window has been exposed.
-                       sdlHandler->update_video_paused(exposed);
-
-                       // Don't run any frames.
-                       continue;
-               }
-
-               // Clear the 'exposed' flag.
-               exposed = false;
-
-               // New start time.
-               clks.new_clk = timing.getTime();
-
-               // Update the FPS counter.
-               unsigned int fps_tmp = ((clks.new_clk - clks.fps_clk) & 0x3FFFFF);
-               if (fps_tmp >= 1000000) {
-                       // More than 1 second has passed.
-                       clks.fps_clk = clks.new_clk;
-                       // FIXME: Just use abs() here.
-                       if (clks.frames_old > clks.frames) {
-                               clks.fps = (clks.frames_old - clks.frames);
-                       } else {
-                               clks.fps = (clks.frames - clks.frames_old);
-                       }
-                       clks.frames_old = clks.frames;
-
-                       // Update the window title.
-                       // TODO: Average the FPS over multiple seconds
-                       // and/or quarter-seconds.
-                       char win_title[256];
-                       snprintf(win_title, sizeof(win_title), "Gens/GS II [SDL] - %u fps", clks.fps);
-                       sdlHandler->set_window_title(win_title);
-               }
-
-               if (crazyEffect) {
-                       // "Crazy" Effect is enabled.
-                       // Don't run emulation; instead, just apply
-                       // the "Crazy" Effect to the framebuffer.
-                       crazyEffect->run(crazyFb);
-                       sdlHandler->update_video();
-                       clks.frames++;
-                       yield();
-                       // TODO: Use the frameskip code to limit frames?
-                       continue;
-               }
-
-               // Frameskip.
-               if (frameskip) {
-                       // Determine how many frames to run.
-                       clks.usec_frameskip += ((clks.new_clk - clks.old_clk) & 0x3FFFFF); // no more than 4 secs
-                       unsigned int frames_todo = (unsigned int)(clks.usec_frameskip / usec_per_frame);
-                       clks.usec_frameskip %= usec_per_frame;
-                       clks.old_clk = clks.new_clk;
-
-                       if (frames_todo == 0) {
-                               // No frames to do yet.
-                               // Wait until the next frame.
-                               uint64_t usec_sleep = (usec_per_frame - clks.usec_frameskip);
-                               if (usec_sleep > 1000) {
-                                       // Never sleep for longer than the 50 Hz value
-                                       // so events are checked often enough.
-                                       if (usec_sleep > (1000000 / 50)) {
-                                               usec_sleep = (1000000 / 50);
-                                       }
-                                       usec_sleep -= 1000;
-
-#ifdef _WIN32
-                                       // Win32: Use a yield() loop.
-                                       // FIXME: Doesn't work properly on VBox/WinXP...
-                                       uint64_t yield_end = timing.getTime() + usec_sleep;
-                                       do {
-                                               yield();
-                                       } while (yield_end > timing.getTime());
-#else /* !_WIN32 */
-                                       // Linux: Use usleep().
-                                       usleep(usec_sleep);
-#endif /* _WIN32 */
-                               }
-                       } else {
-                               // Draw frames.
-                               for (; frames_todo != 1; frames_todo--) {
-                                       // Run a frame without rendering.
-                                       context->execFrameFast();
-                                       sdlHandler->update_audio();
-                               }
-                               frames_todo = 0;
-
-                               // Run a frame and render it.
-                               context->execFrame();
-                               sdlHandler->update_audio();
-                               sdlHandler->update_video();
-                               // Increment the frame counter.
-                               clks.frames++;
-
-                               // Autosave SRAM/EEPROM.
-                               // TODO: EmuContext::execFrame() should probably do this itself...
-                               context->autoSaveData(1);
-                       }
-               } else {
-                       // Run a frame and render it.
-                       context->execFrame();
-                       sdlHandler->update_audio();
-                       sdlHandler->update_video();
-                       // Increment the frame counter.
-                       clks.frames++;
-
-                       // Autosave SRAM/EEPROM.
-                       // TODO: EmuContext::execFrame() should probably do this itself...
-                       context->autoSaveData(1);
-               }
-
-               // Update the I/O manager.
-               keyManager->updateIoManager(context->m_ioManager);
-       }
+       // COMMIT CHECK: How many linebreaks were here?
 
        // Pause audio and wait 50ms for SDL to catch up.
        sdlHandler->pause_audio(true);
        usleep(50000);
 
-       // Save SRAM/EEPROM, if necessary.
-       // TODO: Move to EmuContext::~EmuContext()?
-       context->saveData();
-
        // NULL out the VBackend before shutting down SDL.
        vBackend = nullptr;
 
        // Unregister the LibGens OSD handler.
        lg_set_osd_fn(nullptr);
 
-       // NOTE: Deleting sdlHandler can cause crashes on Windows
-       // due to the timer callback trying to post the semaphore
-       // after it's been deleted.
-       // Shut down the SDL functions manually.
-       sdlHandler->end_audio();
-       sdlHandler->end_video();
-       //delete sdlHandler;
-
-       // If we were running the "Crazy" Effect,
-       // unreference its framebuffer.
-       if (crazyFb) {
-               crazyFb->unref();
+       if (sdlHandler) {
+               // NOTE: Deleting sdlHandler can cause crashes on Windows
+               // due to the timer callback trying to post the semaphore
+               // after it's been deleted.
+               // Shut down the SDL functions manually.
+               sdlHandler->end_audio();
+               sdlHandler->end_video();
+               //delete sdlHandler;
        }
-       delete crazyEffect;
 
-       // Shut. Down. EVERYTHING.
-       delete keyManager;
-       delete context;
-       delete rom;
-       fb->unref();
-       return EXIT_SUCCESS;
+       // ...and we're done here.
+       return ret;
 }
 
 }
@@ -1066,6 +477,8 @@ int main(int argc, char *argv[])
        }
 #endif /* _WIN32 */
 
+       // TODO: Use popt; don't require a ROM filename if
+       // using "Crazy" Effect mode.
        if (argc < 2) {
                fprintf(stderr, "usage: %s [rom filename]\n", argv[0]);
                return EXIT_FAILURE;
diff --git a/src/gens-sdl/gens-sdl.hpp b/src/gens-sdl/gens-sdl.hpp
new file mode 100644 (file)
index 0000000..ea556bb
--- /dev/null
@@ -0,0 +1,110 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * gens-sdl.hpp: Entry point.                                              *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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 the   *
+ * Free Software Foundation; either version 2 of the License, or (at your  *
+ * option) any later version.                                              *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of              *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+#ifndef __GENS_SDL_HPP__
+#define __GENS_SDL_HPP__
+// TODO: Don't include SDL.h here?
+#include <SDL.h>
+
+// LibGens::Timing
+// TODO: Split clks_t into a separate header file?
+#include "libgens/Util/Timing.hpp"
+
+namespace GensSdl {
+
+class SdlHandler;
+extern SdlHandler *sdlHandler;
+
+class VBackend;
+extern VBackend *vBackend;
+
+extern bool running;
+
+union paused_t {
+       struct {
+               uint8_t manual  : 1;    // Manual pause.
+               uint8_t focus   : 1;    // Auto pause when focus is lost.
+       };
+       uint8_t data;
+};
+extern paused_t paused;
+
+// Enable frameskip.
+extern bool frameskip;
+
+// Window has been exposed.
+// Video should be updated if emulation is paused.
+extern bool exposed;
+
+// Timing object.
+extern LibGens::Timing timing;
+
+class clks_t {
+       public:
+               // Reset frameskip timers.
+               void reset(void) {
+                       start_clk = timing.getTime();
+                       old_clk = start_clk;
+                       fps_clk = start_clk;
+                       fps_clk = start_clk;
+                       new_clk = start_clk;
+                       usec_frameskip = 0;
+
+                       // Frame counter.
+                       frames = 0;
+                       frames_old = 0;
+                       fps = 0;
+               }
+
+               uint64_t start_clk;
+               uint64_t old_clk;
+               uint64_t fps_clk;
+               uint64_t new_clk;
+               // Microsecond counter for frameskip.
+               uint64_t usec_frameskip;
+
+               // Frame counters.
+               unsigned int frames;
+               unsigned int frames_old;
+               unsigned int fps;       // TODO: float or double?
+};
+extern clks_t clks;
+
+/**
+ * Check for any OSD messages that were printed during startup.
+ * These messages would have been printed before VBackend
+ * was initialized, so they had to be temporarily stored.
+ */
+void checkForStartupMessages(void);
+
+/**
+ * Process an SDL event.
+ * EmuLoop processes SDL events first; if it ends up
+ * with an event that it can't handle, it goes here.
+ * @param event SDL event.
+ * @return 0 if the event was handled; non-zero if it wasn't.
+ */
+int processSdlEvent_common(const SDL_Event *event);
+
+}
+
+#endif /* __GENS_SDL_HPP__ */