From 27243323fd5a91f4c95bcf116b1da93c0a42ea4d Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Fri, 12 Dec 2014 21:21:48 +1100 Subject: [PATCH] Add PNaCl build of VBA-M. This CL adds a PNaCl target for VBA-M which builds a Chrome App into the src/pnacl/app directory. --- CMakeLists.txt | 43 +++-- CMakeScripts/PNaCl.Toolchain.cmake | 56 ++++++ src/common/Patch.cpp | 4 +- src/gba/GBAcpu.h | 2 +- src/pnacl/app/background.js | 11 ++ src/pnacl/app/game.html | 63 ++++++ src/pnacl/app/game.nmf | 10 + src/pnacl/app/icon.png | Bin 0 -> 21397 bytes src/pnacl/app/index.js | 237 +++++++++++++++++++++++ src/pnacl/app/manifest.json | 22 +++ src/pnacl/app/style.css | 89 +++++++++ src/pnacl/app/vbam.cfg | 5 + src/pnacl/nacl_glue.cpp | 300 +++++++++++++++++++++++++++++ src/sdl/SDL.cpp | 39 +++- src/sdl/inputSDL.cpp | 12 +- 15 files changed, 866 insertions(+), 27 deletions(-) create mode 100644 CMakeScripts/PNaCl.Toolchain.cmake create mode 100644 src/pnacl/app/background.js create mode 100644 src/pnacl/app/game.html create mode 100644 src/pnacl/app/game.nmf create mode 100644 src/pnacl/app/icon.png create mode 100644 src/pnacl/app/index.js create mode 100644 src/pnacl/app/manifest.json create mode 100644 src/pnacl/app/style.css create mode 100644 src/pnacl/app/vbam.cfg create mode 100644 src/pnacl/nacl_glue.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 394799dc..79678cf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,13 @@ if( NOT ENABLE_DEBUGGER AND ENABLE_SDL ) message( SEND_ERROR "The SDL port can't be built without debugging support" ) endif( NOT ENABLE_DEBUGGER AND ENABLE_SDL ) +if ( PNACL ) + set ( ENABLE_SDL ON ) + set ( ENABLE_GTK OFF ) + set ( ENABLE_LINK OFF ) + set ( ENABLE_WX OFF ) +endif ( PNACL ) + # Set the version number with -DVERSION=X.X.X-uber IF( NOT VERSION ) SET( VERSION "https://github.com/EmperorArthur/VBA-M" ) @@ -71,7 +78,7 @@ endif( ENABLE_ASM_SCALERS ) # Look for some dependencies using CMake scripts FIND_PACKAGE ( ZLIB REQUIRED ) FIND_PACKAGE ( PNG REQUIRED ) -FIND_PACKAGE ( OpenGL REQUIRED ) +FIND_PACKAGE ( OpenGL ) FIND_PACKAGE ( SDL REQUIRED ) if( ENABLE_LINK ) @@ -87,7 +94,10 @@ SET(VBAMCORE_LIBS ${ZLIB_LIBRARY} ${PNG_LIBRARY}) - +if ( PNACL ) + # Add core NaCl libraries. + SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ppapi ppapi_cpp nacl_io) +endif ( PNACL ) # Disable looking for GTK if not going to build the GTK frontend # so that pkg-config is not required @@ -131,8 +141,12 @@ IF( NOT SYSCONFDIR ) SET( SYSCONFDIR "/etc" ) ENDIF( NOT SYSCONFDIR ) +if ( OpenGL_FOUND ) + set( USE_OPENGL_FLAG "-DUSE_OPENGL " ) +endif ( OpenGL_FOUND ) + # C defines -ADD_DEFINITIONS (-DHAVE_NETINET_IN_H -DHAVE_ARPA_INET_H -DHAVE_ZLIB_H -DFINAL_VERSION -DSDL -DUSE_OPENGL -DSYSCONFDIR='"${SYSCONFDIR}"' -DWITH_LIRC='${WITHLIRC}') +ADD_DEFINITIONS (-DHAVE_NETINET_IN_H -DHAVE_ARPA_INET_H -DHAVE_ZLIB_H -DFINAL_VERSION -DSDL ${USE_OPENGL_FLAG} -DSYSCONFDIR='"${SYSCONFDIR}"' -DWITH_LIRC='${WITHLIRC}') ADD_DEFINITIONS (-DVERSION='"${VERSION}"' -DPKGDATADIR='"${PKGDATADIR}"' -DPACKAGE='') if( ENABLE_LINK ) @@ -142,13 +156,13 @@ if( ENABLE_LINK ) SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${RT_LIB}) SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${RT_LIB}) ENDIF(RT_LIB) - + FIND_LIBRARY(PTHREAD_LIB pthread) IF(PTHREAD_LIB) SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${PTHREAD_LIB}) SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${PTHREAD_LIB}) ENDIF(PTHREAD_LIB) - + INCLUDE(CheckFunctionExists) CHECK_FUNCTION_EXISTS(sem_timedwait SEM_TIMEDWAIT) IF( SEM_TIMEDWAIT) @@ -304,6 +318,10 @@ SET(SRC_SDL src/sdl/expr-lex.cpp ) +SET(SRC_PNACL + src/pnacl/nacl_glue.cpp +) + SET(SRC_FILTERS src/filters/2xSaI.cpp src/filters/admame.cpp @@ -376,8 +394,8 @@ IF( ENABLE_SDL ) vbam WIN32 ${SRC_SDL} + ${SRC_PNACL} ) - IF( WIN32 ) SET( WIN32_LIBRARIES wsock32 ) ENDIF( WIN32 ) @@ -392,11 +410,14 @@ IF( ENABLE_SDL ) ${WIN32_LIBRARIES} ${LIRC_CLIENT_LIBRARY} ) - - INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vbam DESTINATION bin) - INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/sdl/vbam.cfg-example - DESTINATION ${SYSCONFDIR} - RENAME vbam.cfg) + IF ( PNACL ) + build_to_app( vbam ) + ELSE ( PNACL ) + INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vbam DESTINATION bin) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/sdl/vbam.cfg-example + DESTINATION ${SYSCONFDIR} + RENAME vbam.cfg) + ENDIF ( PNACL ) ENDIF( ENABLE_SDL ) IF( ENABLE_GTK ) diff --git a/CMakeScripts/PNaCl.Toolchain.cmake b/CMakeScripts/PNaCl.Toolchain.cmake new file mode 100644 index 00000000..6bd1c659 --- /dev/null +++ b/CMakeScripts/PNaCl.Toolchain.cmake @@ -0,0 +1,56 @@ +# How to build for PNaCl on Linux. +# +# Download the Native Client SDK. +# Run naclsdk update pepper_XX. The pepper version must be pepper_37. +# Set NACL_SDK_ROOT in your environment to nacl_sdk/pepper_XX. +# +# Check out the naclports repository. This was built with +# naclports@b0aaa9899316e157883ee4b54f70affbebd2c3e1. +# Set NACL_ARCH to 'pnacl' in your environment and run 'make sdl' +# from the naclports root. +# +# Come back to the root of vba-m and run +# cmake -DCMAKE_TOOLCHAIN_FILE=CMakeScripts/PNaCl.Toolchain.cmake CMakeLists.txt +# make +# +# This will build a non-finalized PNaCl port to ./vbam and a finalized version +# to src/pnacl/app/vbam.pexe. +# +# The src/pnacl/app folder can be loaded as an unpacked extension into Chrome +# which will run vba-m as a packaged app. + +include( CMakeForceCompiler ) + +set( PNACL ON ) +set( PLATFORM_PREFIX "$ENV{NACL_SDK_ROOT}/toolchain/linux_pnacl" ) +set( FINALIZED_TARGET "src/pnacl/app/vbam.pexe" ) + +set( CMAKE_SYSTEM_NAME "Linux" CACHE STRING "Target system." ) +set( CMAKE_SYSTEM_PROCESSOR "LLVM-IR" CACHE STRING "Target processor." ) +set( CMAKE_FIND_ROOT_PATH "${PLATFORM_PREFIX}/usr" ) +set( CMAKE_AR "${PLATFORM_PREFIX}/bin64/pnacl-ar" CACHE STRING "") +set( CMAKE_RANLIB "${PLATFORM_PREFIX}/bin64/pnacl-ranlib" CACHE STRING "") +set( CMAKE_C_COMPILER "${PLATFORM_PREFIX}/bin64/pnacl-clang" ) +set( CMAKE_CXX_COMPILER "${PLATFORM_PREFIX}/bin64/pnacl-clang++" ) +set( CMAKE_C_FLAGS "-Wno-non-literal-null-conversion -Wno-deprecated-writable-strings -U__STRICT_ANSI__" CACHE STRING "" ) +set( CMAKE_CXX_FLAGS "-Wno-non-literal-null-conversion -Wno-deprecated-writable-strings -U__STRICT_ANSI__" CACHE STRING "" ) + +cmake_force_c_compiler( ${CMAKE_C_COMPILER} Clang ) +cmake_force_cxx_compiler( ${CMAKE_CXX_COMPILER} Clang ) + +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY ) + +macro( build_to_app _target ) + add_custom_command( TARGET ${_target} + POST_BUILD + COMMAND "${PLATFORM_PREFIX}/bin64/pnacl-finalize" + "-o" "${FINALIZED_TARGET}" + "$" ) +endmacro() + +set( ENV{SDLDIR} "${PLATFORM_PREFIX}/usr" ) +include_directories( SYSTEM $ENV{NACL_SDK_ROOT}/include ) +link_directories( $ENV{NACL_SDK_ROOT}/lib/pnacl/Release ) diff --git a/src/common/Patch.cpp b/src/common/Patch.cpp index ac4893a7..9b8ccb6e 100644 --- a/src/common/Patch.cpp +++ b/src/common/Patch.cpp @@ -7,13 +7,13 @@ #ifdef __GNUC__ -#if defined(__APPLE__) || defined (BSD) || defined (__NetBSD__) +#if defined(__APPLE__) || defined (BSD) || defined (__NetBSD__) || defined (__native_client__) typedef off_t __off64_t; /* off_t is 64 bits on BSD. */ #define fseeko64 fseeko #define ftello64 ftello #else typedef off64_t __off64_t; -#endif /* __APPLE__ || BSD */ +#endif /* __APPLE__ || BSD || __native_client__ */ #endif /* __GNUC__ */ #ifndef _MSC_VER diff --git a/src/gba/GBAcpu.h b/src/gba/GBAcpu.h index 3b22e007..6687f267 100644 --- a/src/gba/GBAcpu.h +++ b/src/gba/GBAcpu.h @@ -5,7 +5,7 @@ extern int armExecute(); extern int thumbExecute(); #ifdef __GNUC__ -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(__native_client__) # define INSN_REGPARM __attribute__((regparm(1))) #else # define INSN_REGPARM /*nothing*/ diff --git a/src/pnacl/app/background.js b/src/pnacl/app/background.js new file mode 100644 index 00000000..e9d8ab4c --- /dev/null +++ b/src/pnacl/app/background.js @@ -0,0 +1,11 @@ +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('game.html', { + bounds: { + width: 480, + height: 320 + }, + resizable: true, + minWidth: 480, + minHeight: 320 + }); +}); diff --git a/src/pnacl/app/game.html b/src/pnacl/app/game.html new file mode 100644 index 00000000..d9b0f419 --- /dev/null +++ b/src/pnacl/app/game.html @@ -0,0 +1,63 @@ + + + + + + +
+ +
+
+
Translating PNaCl to Native Architecture
+
+
+
+
+
+
Press Ctrl+O to open a rom
+
+ The filename cannot have spaces. Please rename the file and try again. +
+
+
+
+
+
Advanced
+
+
Import save
+
+ +
+
+
+
+
A: Z
+
B: X
+
L: A
+
R: S
+
D-pad: Arrow keys
+
+
+
Start: Enter
+
Select: Backspace
+
+
Save state: Shift + F1-9
+
Load state: F1-9
+
+
+
Save state: Shift + Search(🔍) + 1-9
+
Load state: Search(🔍) + 1-9
+
+
Vol down/up: -/=
+
+ + + + diff --git a/src/pnacl/app/game.nmf b/src/pnacl/app/game.nmf new file mode 100644 index 00000000..0c537963 --- /dev/null +++ b/src/pnacl/app/game.nmf @@ -0,0 +1,10 @@ +{ + "program": { + "portable" : { + "pnacl-translate" : { + "url" : "vbam.pexe", + "optlevel": 2 + } + } + } +} diff --git a/src/pnacl/app/icon.png b/src/pnacl/app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..af50b54484e36befceeea6933e7f09dc431632ff GIT binary patch literal 21397 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 div { + background-color: blue; + border-radius: 10px; + height: 10px; + width: 0; +} + +#naclModule { + -webkit-transform-origin: 50% 50%; +} + +#leftHelptext { + bottom: 5px; + left: 5px; + position: fixed; +} + +#rightHelptext { + bottom: 5px; + position: fixed; + right: 5px; + text-align: right; +} + +#topLeftHelptext { + left: 5px; + position: fixed; + text-align: left; + top: 5px; +} + +#topRightHelptext { + top: 5px; + position: fixed; + right: 5px; + text-align: right; +} + +.advancedSetting { + display: none; +} diff --git a/src/pnacl/app/vbam.cfg b/src/pnacl/app/vbam.cfg new file mode 100644 index 00000000..4b29df00 --- /dev/null +++ b/src/pnacl/app/vbam.cfg @@ -0,0 +1,5 @@ +captureDir=/store/states +saveDir=/store/states +batteryDir=/store/states +showSpeed=2 +rtcEnabled=1 diff --git a/src/pnacl/nacl_glue.cpp b/src/pnacl/nacl_glue.cpp new file mode 100644 index 00000000..44d1f805 --- /dev/null +++ b/src/pnacl/nacl_glue.cpp @@ -0,0 +1,300 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUM_BUTTONS 17 +#define AXIS_MAX 32767 + +extern int SDL_main(int argc, char **argv); + +class GameInstance : public pp::Instance { + public: + explicit GameInstance(PP_Instance instance) + : pp::Instance(instance), + game_main_thread_(NULL), + num_changed_view_(0), + width_(0), + height_(0), + gamepad_enabled_(false), + cc_factory_(this) { + // Game requires mouse and keyboard events; add more if necessary. + nacl_io_init_ppapi(instance, pp::Module::Get()->get_browser_interface()); + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | + PP_INPUTEVENT_CLASS_KEYBOARD); + ++num_instances_; + assert(num_instances_ == 1); + + gamepad_ = static_cast( + pp::Module::Get()->GetBrowserInterface(PPB_GAMEPAD_INTERFACE)); + assert(gamepad_); + } + + virtual ~GameInstance() { + // Wait for game thread to finish. + if (game_main_thread_) { + pthread_join(game_main_thread_, NULL); + } + } + + // This function is called with the HTML attributes of the embed tag, + // which can be used in lieu of command line arguments. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + for (uint32_t i = 0; i < argc; ++i) { + if (argn[i] == std::string("width")) { + width_ = strtol(argv[i], 0, 0); + } + + if (argn[i] == std::string("height")) { + height_ = strtol(argv[i], 0, 0); + } + } + return true; + } + + // This crucial function forwards PPAPI events to SDL. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + SDL_NACL_PushEvent(event.pp_resource()); + return true; + } + + virtual void HandleMessage(const pp::Var& message) { + pp::VarDictionary args(message); + pp::Var filesystem_var = args.Get("filesystem"); + if (!filesystem_var.is_resource()) + return; + + pp::Resource filesystem_resource = filesystem_var.AsResource(); + if (!pp::FileSystem::IsFileSystem(filesystem_resource)) + return; + + PP_Resource filesystem_id = filesystem_resource.pp_resource(); + if (!filesystem_id) + return; + + char buf[BUFSIZ]; + if (snprintf(buf, BUFSIZ, "type=PERSISTENT,filesystem_resource=%d", filesystem_id) > BUFSIZ) + return; + + rom_mount_string_ = buf; + + pp::Var path_var = args.Get("path"); + if (!path_var.is_string()) + return; + + rom_path_string_ = path_var.AsString(); + + pp::Var gamepad_var = args.Get("gamepad"); + if (!gamepad_var.is_bool()) + return; + + gamepad_enabled_ = gamepad_var.AsBool(); + } + + // This function is called for various reasons, e.g. visibility and page + // size changes. We ignore these calls except for the first + // invocation, which we use to start the game. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + ++num_changed_view_; + if (num_changed_view_ > 1) return; + // NOTE: It is crucial that the two calls below are run here + // and not in a thread. + SDL_NACL_SetInstance(pp_instance(), + pp::Module::Get()->get_browser_interface(), + width_, + height_); + // This is SDL_Init call which used to be in game_main() + int flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | + SDL_INIT_NOPARACHUTE | SDL_INIT_JOYSTICK; + if(SDL_Init(flags)) + exit(-1); + + StartGameInNewThread(0); + } + private: + static int num_instances_; // Ensure we only create one instance. + static bool old_pressed_[NUM_BUTTONS]; + + pthread_t game_main_thread_; // This thread will run game_main(). + int num_changed_view_; // Ensure we initialize an instance only once. + // Dimension of the SDL video screen. + int width_; + int height_; + pp::CompletionCallbackFactory cc_factory_; + bool gamepad_enabled_; + const PPB_Gamepad* gamepad_; + + std::string rom_mount_string_; + std::string rom_path_string_; + + static SDL_Event *copyEvent(SDL_Event& event) { + SDL_Event *event_copy = (SDL_Event*)malloc(sizeof(SDL_Event)); + *event_copy = event; + return event_copy; + } + + int GetSDLButton(int i) { + switch (i) { + case 2: + return 1; + case 3: + return 0; + case 4: + return 6; + case 5: + return 7; + } + return i; + } + + void GamepadInputLoop(int32_t dummy) { + PP_GamepadsSampleData gamepad_data; + gamepad_->Sample(pp_instance(), &gamepad_data); + if (gamepad_data.length) { + PP_GamepadSampleData& pad = gamepad_data.items[0]; + if (pad.connected) { + // Draw axes. + SDL_Event e; + bool pressed[NUM_BUTTONS] = {0}; + for (int i = 0; i < pad.buttons_length; i++) + pressed[i] = pad.buttons[i] > 0.3 || pressed[i]; + + if (old_pressed_[16] != pressed[16]) { + } + for (int i = 0; i < NUM_BUTTONS; i++) { + if (pressed[i] != old_pressed_[i]) { + e.type = SDL_JOYBUTTONDOWN; + e.jbutton.which = 0; + e.jbutton.button = GetSDLButton(i); + e.jbutton.state = pressed[i] ? SDL_PRESSED : SDL_RELEASED; + SDL_PushEvent(copyEvent(e)); + old_pressed_[i] = pressed[i]; + // Send keyboard SPACE instead of button 16. + if (i == 16) { + e.type = pressed[i] ? SDL_KEYDOWN : SDL_KEYUP; + e.key.type = pressed[i] ? SDL_KEYDOWN : SDL_KEYUP; + e.key.which = 1; + e.key.state = pressed[i] ? SDL_PRESSED : SDL_RELEASED; + e.key.keysym.scancode = SDLK_SPACE; + e.key.keysym.sym = SDLK_SPACE; + SDL_PushEvent(copyEvent(e)); + } + } + } + + e.type = SDL_JOYAXISMOTION; + e.jaxis.which = 0; + for (int i = 0; i < pad.axes_length; i++) { + e.jaxis.type = SDL_JOYAXISMOTION; + e.jaxis.axis = i; + e.jaxis.value = pad.axes[i] * AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[12]) { + e.jaxis.axis = 1; + e.jaxis.value = -AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[14]) { + e.jaxis.axis = 0; + e.jaxis.value = -AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[13]) { + e.jaxis.axis = 1; + e.jaxis.value = AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + if (pressed[15]) { + e.jaxis.axis = 0; + e.jaxis.value = AXIS_MAX; + SDL_PushEvent(copyEvent(e)); + } + } + } + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::GamepadInputLoop), 0); + } + + void StartGameInNewThread(int32_t dummy) { + if (!rom_mount_string_.empty() && !rom_path_string_.empty()) { + fprintf(stderr, "Mounting %s with %s\n", rom_path_string_.c_str(), + rom_mount_string_.c_str()); + pthread_create(&game_main_thread_, NULL, &LaunchGame, this); + if (gamepad_enabled_) { + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::GamepadInputLoop), 0); + } + } else { + // Wait some more (here: 100ms). + pp::Module::Get()->core()->CallOnMainThread( + 100, cc_factory_.NewCallback(&GameInstance::StartGameInNewThread), 0); + } + } + + static void* LaunchGame(void* data) { + // Use "thiz" to get access to instance object. + GameInstance* thiz = reinterpret_cast(data); + umount("/"); + mount("", "/", "memfs", 0, ""); + + mkdir("/config", 0777); + fprintf(stderr, "mount httpfs = %d\n", mount("/", "/config", "httpfs", 0, "")); + + mkdir("/games", 0777); + fprintf(stderr, "mount rom = %d\n", mount("/", "/games", "html5fs", 0, thiz->rom_mount_string_.c_str())); + + mkdir("/store", 0777); + fprintf(stderr, "mount html5fs = %d\n", mount("/", "/store", "html5fs", 0, "")); + mkdir("/store/states", 0777); + mkdir("/store/captures", 0777); + // Craft a fake command line. + char rom_path[BUFSIZ]; + strncpy(rom_path, ("/games" + thiz->rom_path_string_).c_str(), BUFSIZ); + std::string config_string("--config=/config/vbam.cfg"); + if (thiz->gamepad_enabled_) + config_string = "--config=/config/vbam-gamepad.cfg"; + + char config[BUFSIZ]; + strncpy(config, config_string.c_str(), BUFSIZ); + char* argv[] = { "vbam", config, rom_path, NULL}; + SDL_main(3, argv); + return 0; + } +}; + +int GameInstance::num_instances_; +bool GameInstance::old_pressed_[]; + +class GameModule : public pp::Module { + public: + GameModule() : pp::Module() {} + virtual ~GameModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new GameInstance(instance); + } +}; + +namespace pp { +Module* CreateModule() { + return new GameModule(); +} + +} // namespace pp diff --git a/src/sdl/SDL.cpp b/src/sdl/SDL.cpp index 68cafd0e..782045bd 100644 --- a/src/sdl/SDL.cpp +++ b/src/sdl/SDL.cpp @@ -23,6 +23,7 @@ #include #include #include +#ifdef USE_OPENGL #ifdef __APPLE__ #include #include @@ -30,6 +31,7 @@ #include #include #endif +#endif #include @@ -134,9 +136,13 @@ int sdlPrintUsage = 0; int cartridgeType = 3; int captureFormat = 0; -int openGL = 1; int textureSize = 256; +#ifdef USE_OPENGL +int openGL = 1; GLuint screenTexture = 0; +#else +int openGL = 0; +#endif u8 *filterPix = 0; int pauseWhenInactive = 0; @@ -810,6 +816,7 @@ void sdlReadPreferences(FILE *f) void sdlOpenGLInit(int w, int h) { +#ifdef USE_OPENGL float screenAspect = (float) srcWidth / srcHeight, windowAspect = (float) w / h; @@ -861,6 +868,7 @@ void sdlOpenGLInit(int w, int h) glClearColor(0.0,0.0,0.0,1.0); glClear( GL_COLOR_BUFFER_BIT ); +#endif } void sdlReadPreferences() @@ -1277,6 +1285,9 @@ static void sdlHandleSavestateKey(int num, int shifted) // 0: load // 1: save int backuping = 1; // controls whether we are doing savestate backups +#ifdef __native_client__ + backuping = 0; +#endif if ( sdlSaveKeysSwitch == 2 ) { @@ -1475,9 +1486,11 @@ void sdlPollEvents() } break; case SDLK_KP_DIVIDE: + case SDLK_MINUS: sdlChangeVolume(-0.1); break; case SDLK_KP_MULTIPLY: + case SDLK_EQUALS: sdlChangeVolume(0.1); break; case SDLK_KP_MINUS: @@ -1543,15 +1556,19 @@ void sdlPollEvents() case SDLK_g: if(!(event.key.keysym.mod & MOD_NOCTRL) && (event.key.keysym.mod & KMOD_CTRL)) { - filterFunction = 0; - while (!filterFunction) - { - filter = (Filter)((filter + 1) % kInvalidFilter); - filterFunction = initFilter(filter, systemColorDepth, srcWidth); - } - if (getFilterEnlargeFactor(filter) != filter_enlarge) - sdlInitVideo(); - systemScreenMessage(getFilterName(filter)); + filterFunction = 0; + while (!filterFunction) { + filter = (Filter)((filter + 1) % kInvalidFilter); +#ifdef __native_client__ + if (getFilterEnlargeFactor(filter) != filter_enlarge) + continue; +#endif + filterFunction = initFilter(filter, systemColorDepth, srcWidth); + } + if (getFilterEnlargeFactor(filter) != filter_enlarge) { + sdlInitVideo(); + } + systemScreenMessage(getFilterName(filter)); } break; case SDLK_F11: @@ -2486,6 +2503,7 @@ void systemDrawScreen() drawSpeed(screen, destPitch, 10, 20); if (openGL) { +#ifdef USE_OPENGL glClear( GL_COLOR_BUFFER_BIT ); glPixelStorei(GL_UNPACK_ROW_LENGTH, destWidth); if (systemColorDepth == 16) @@ -2508,6 +2526,7 @@ void systemDrawScreen() glEnd(); SDL_GL_SwapBuffers(); +#endif } else { SDL_UnlockSurface(surface); SDL_Flip(surface); diff --git a/src/sdl/inputSDL.cpp b/src/sdl/inputSDL.cpp index e0ba0ca5..1360e91b 100644 --- a/src/sdl/inputSDL.cpp +++ b/src/sdl/inputSDL.cpp @@ -85,7 +85,7 @@ static int sensorY = 2047; static uint32_t sdlGetHatCode(const SDL_Event &event) { if (!event.jhat.value) return 0; - + return ( ((event.jhat.which + 1) << 16) | 32 | @@ -249,7 +249,6 @@ static void sdlUpdateJoyButton(int which, int b = joypad[j][i] & 0xffff; if(dev) { dev--; - if((dev == which) && (b >= 128) && (b == (button+128))) { sdlButtons[j][i] = pressed; } @@ -399,7 +398,11 @@ void inputInitJoysticks() joypad[PAD_MAIN][i] = joypad[PAD_DEFAULT][i]; } +#if defined (__native_client__) + sdlNumDevices = 1; +#else sdlNumDevices = SDL_NumJoysticks(); +#endif if(sdlNumDevices) sdlDevices = (SDL_Joystick **)calloc(1,sdlNumDevices * @@ -418,8 +421,11 @@ void inputInitJoysticks() if(sdlDevices[dev] == NULL) { sdlDevices[dev] = SDL_JoystickOpen(dev); } - +#if defined (__native_client__) + ok = true; // Force gamepad to be useable. +#else ok = sdlCheckJoyKey(joypad[j][i]); +#endif } else ok = false; }