diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fcf242..745b91b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ set(_vpx_valid_combos GL-windows GL-macos GL-linux + GL-emscripten DX9-windows) if(NOT "${RENDERER}-${PLATFORM}" IN_LIST _vpx_valid_combos) string(REPLACE ";" "\n " _vpx_available "${_vpx_valid_combos}") @@ -784,6 +785,141 @@ elseif(PLATFORM STREQUAL "linux") endif() endif() +elseif(PLATFORM STREQUAL "emscripten") + # Static wasm32 dependency libs/headers staged by vpinball-wasm's + # scripts/build-deps.sh (SDL3/SDL3_image/SDL3_ttf/FreeImage/libwinevbs + # built via emcmake). No RPATH concept applies to a wasm binary. + if(NOT DEFINED VPINBALL_WASM_DEPS_DIR) + set(VPINBALL_WASM_DEPS_DIR "${CMAKE_SOURCE_DIR}/../build/deps-wasm32") + endif() + + if(RENDERER STREQUAL "GL") + add_library(glad + third-party/include/glad/src/gles2.c + ) + set_target_properties(glad PROPERTIES LINKER_LANGUAGE C) + target_include_directories(glad PUBLIC + third-party/include/glad/include + ) + endif() + + # No raw HID device access in a browser sandbox (real-hardware nudge/plunger + # boards) - hidapi-hidraw is not linked for this platform, so exclude its + # one consumer. + list(REMOVE_ITEM VPX_STANDALONE_SOURCES + src/input/OpenPinDevHandler.cpp + ) + + add_executable(vpinball + ${VPX_STANDALONE_SOURCES} + ${CMAKE_SOURCE_DIR}/src/core/EmscriptenBridge.cpp + ) + + target_include_directories(vpinball PUBLIC + ${CMAKE_SOURCE_DIR}/third-party/include + ${CMAKE_SOURCE_DIR} + ${VPINBALL_WASM_DEPS_DIR}/include + ${VPINBALL_WASM_DEPS_DIR}/include/libwinevbs/atl/include + ${VPINBALL_WASM_DEPS_DIR}/include/libwinevbs/atlmfc/include + ${VPINBALL_WASM_DEPS_DIR}/include/libwinevbs/wine/include + src + plugins + ) + + target_compile_definitions(vpinball PRIVATE + __STANDALONE__ + + __LIBWINEVBS__ + __WINESRC__ + WINE_UNICODE_NATIVE + + "__forceinline=__attribute__((always_inline)) inline" + ) + + target_compile_definitions(vpinball PRIVATE + ENABLE_OPENGL + __OPENGLES__ + ) + + target_compile_options(vpinball PUBLIC + -fdenormal-fp-math=preserve-sign + -freciprocal-math + -fassociative-math + -fapprox-func + -fno-math-errno + -fno-trapping-math + -ffp-contract=off + -Wno-shorten-64-to-32 + ) + + target_link_directories(vpinball PUBLIC + "${VPINBALL_WASM_DEPS_DIR}/lib" + ) + + target_link_libraries(vpinball PUBLIC + SDL3 + SDL3_image + SDL3_ttf + freetype + freeimage + winevbs + glad + ) + + set_target_properties(vpinball PROPERTIES + RUNTIME_OUTPUT_NAME "vpinball" + SUFFIX ".js" + ) + + set(RESOURCES_DIR "$") + set(SHADER_DIR_NAME "shaders-${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") + set(SHADER_DIR "${RESOURCES_DIR}/${SHADER_DIR_NAME}") + + # PRE_LINK (not POST_BUILD): the --preload-file linker flags below run + # emscripten's file_packager as part of the link step itself, so the + # staged directories must exist *before* linking, not after. + add_custom_command(TARGET vpinball PRE_LINK + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/src/assets" "${RESOURCES_DIR}/assets" + # vpinball-wasm: exclude editor-only content never read by the + # __STANDALONE__ player runtime (native "File > New" templates and + # mobile-app onboarding screens, and a bundled Monaco code editor for + # remote script editing) - this is ~52MB of the ~54MB raw src/assets + # payload, cut here rather than preloaded and never used. + COMMAND "${CMAKE_COMMAND}" -E rm -f + "${RESOURCES_DIR}/assets/exampleTable.vpx" + "${RESOURCES_DIR}/assets/blankTable.vpx" + "${RESOURCES_DIR}/assets/lightSeqTable.vpx" + "${RESOURCES_DIR}/assets/strippedTable.vpx" + COMMAND "${CMAKE_COMMAND}" -E rm -rf "${RESOURCES_DIR}/assets/web" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "${RESOURCES_DIR}/scripts" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${RESOURCES_DIR}/tables" + # exampleTable.vpx (not test000-default-table.vpx, which is upstream's + # rendering/component regression-test fixture with no gameplay script) + # is a genuine playable demo table - it has real LeftFlipper/RightFlipper + # Animate/Collide subs, Table1_KeyDown/KeyUp, slingshots, bumpers, + # targets, a plunger and drain detection - the right default for a + # public-facing demo. + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/src/assets/exampleTable.vpx" "${RESOURCES_DIR}/tables/default.vpx" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${SHADER_DIR}" + ) + foreach(_shader IN LISTS VPX_GL_SHADERS) + add_custom_command(TARGET vpinball PRE_LINK + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/src/shaders/hlsl_glsl/${_shader}" "${SHADER_DIR}" + ) + endforeach() + + # Embed the staged assets/scripts/shaders/default table into the wasm + # build's virtual filesystem so the engine can open them with ordinary + # fopen()/std::ifstream calls, unmodified, exactly as it does natively. + # Mount points are relative (matching how the native build locates these + # directories next to the executable, i.e. relative to cwd "/" in MEMFS). + target_link_options(vpinball PRIVATE + "SHELL:--preload-file ${RESOURCES_DIR}/assets@assets" + "SHELL:--preload-file ${RESOURCES_DIR}/scripts@scripts" + "SHELL:--preload-file ${RESOURCES_DIR}/tables@tables" + "SHELL:--preload-file ${SHADER_DIR}@${SHADER_DIR_NAME}" + ) + # iOS and Android libvpinball build elseif(PLATFORM STREQUAL "ios" OR PLATFORM STREQUAL "ios-simulator" OR PLATFORM STREQUAL "android") @@ -957,4 +1093,9 @@ elseif(PLATFORM STREQUAL "ios" OR PLATFORM STREQUAL "ios-simulator" OR PLATFORM endif() -include("${CMAKE_SOURCE_DIR}/make/CMakeLists_plugins.txt") +# vpinball-wasm: plugins are all optional/native-hardware-integration-scoped +# (PinMAME, DOF, B2S, FlexDMD, Inspector, FFmpeg-dependent, etc.) and don't +# map to a browser sandbox for this project's v1 - see README's roadmap. +if(NOT PLATFORM STREQUAL "emscripten") + include("${CMAKE_SOURCE_DIR}/make/CMakeLists_plugins.txt") +endif() diff --git a/src/core/def.h b/src/core/def.h index 7335929..eb2c0b4 100644 --- a/src/core/def.h +++ b/src/core/def.h @@ -337,6 +337,9 @@ static const string platform_bits[2] = { "32"s, "64"s }; #elif defined(__ANDROID__) // leave here, as it also defines linux #define GET_PLATFORM_OS_ENUM 1 #define GET_PLATFORM_OS "android" +#elif defined(__EMSCRIPTEN__) // leave here, as it also defines linux on some toolchains + #define GET_PLATFORM_OS_ENUM 6 + #define GET_PLATFORM_OS "emscripten" #elif (defined(__linux) || defined(__linux__)) #define GET_PLATFORM_OS_ENUM 2 #define GET_PLATFORM_OS "linux" @@ -352,7 +355,7 @@ static const string platform_bits[2] = { "32"s, "64"s }; #define GET_PLATFORM_OS "macos" #endif #endif -static const string platform_os[6] = { "windows"s, "android"s, "linux"s, "ios"s, "tvos"s, "macos"s }; +static const string platform_os[7] = { "windows"s, "android"s, "linux"s, "ios"s, "tvos"s, "macos"s, "emscripten"s }; #if defined(ENABLE_BGFX) #define GET_PLATFORM_RENDERER_ENUM 2 diff --git a/src/core/main.cpp b/src/core/main.cpp index dafe6ee..0015268 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -228,13 +228,14 @@ extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, return retval; } -#if defined(__STANDALONE__) && ((defined(__linux__) && !defined(__ANDROID__)) || defined(__MINGW32__)) +#if defined(__STANDALONE__) && ((defined(__linux__) && !defined(__ANDROID__)) || defined(__MINGW32__) || defined(__EMSCRIPTEN__)) extern int g_argc; extern const char **g_argv; int main(int argc, const char** argv) { #ifdef __MINGW32__ signal(SIGINT, OnSignalHandler); -#else +#elif !defined(__EMSCRIPTEN__) + // No meaningful SIGINT/process-signal equivalent in a browser sandbox. struct sigaction sigIntHandler; sigIntHandler.sa_handler = OnSignalHandler; sigemptyset(&sigIntHandler.sa_mask); diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index 540b822..1af6bd8 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -20,7 +20,8 @@ #include "input/SDLInputHandler.h" -#ifndef __LIBVPINBALL__ +#if !defined(__LIBVPINBALL__) && !defined(__EMSCRIPTEN__) + // No raw HID device access in a browser sandbox. #include "input/OpenPinDevHandler.h" #endif @@ -82,7 +83,7 @@ InputManager::InputManager(Player* player) // Initialize device handlers m_inputHandlers.push_back(std::make_unique(*this)); m_sdlHandler = static_cast(m_inputHandlers.back().get()); - #ifndef __LIBVPINBALL__ + #if !defined(__LIBVPINBALL__) && !defined(__EMSCRIPTEN__) m_inputHandlers.push_back(std::make_unique(*this)); #endif diff --git a/src/physics/kdtree.cpp b/src/physics/kdtree.cpp index 3f262a9..10ac63e 100644 --- a/src/physics/kdtree.cpp +++ b/src/physics/kdtree.cpp @@ -421,7 +421,7 @@ void HitKDNode::HitTestBall(const HitKD* hitoct, const HitBall* const pball, Col if (pball->m_hitBBox.right >= vcenter) m_children[1].HitTestBall(hitoct, pball, coll); } - else f (axis == 1) + else if (axis == 1) { const float vcenter = (m_rectbounds.top+m_rectbounds.bottom)*0.5f; if (pball->m_hitBBox.top <= vcenter) diff --git a/standalone/vpinball_standalone_i.h b/standalone/vpinball_standalone_i.h index 29898aa..3bac681 100644 --- a/standalone/vpinball_standalone_i.h +++ b/standalone/vpinball_standalone_i.h @@ -3351,8 +3351,19 @@ ITableGlobal : public IDispatch virtual HRESULT STDMETHODCALLTYPE put_ShowCursor( VARIANT_BOOL show) = 0; + // vpinball-wasm: mirrors the #ifdef _WIN64 split already used by + // ScriptGlobalTable's own declaration/implementation of this method - + // this generated interface header previously hardcoded the 64-bit + // (SIZE_T) overload unconditionally, which made ScriptGlobalTable + // abstract (unable to override this pure virtual) on 32-bit targets + // like wasm32. +#ifdef _WIN64 virtual HRESULT STDMETHODCALLTYPE get_GetPlayerHWnd( SIZE_T *pVal) = 0; +#else + virtual HRESULT STDMETHODCALLTYPE get_GetPlayerHWnd( + LONG *pVal) = 0; +#endif virtual HRESULT STDMETHODCALLTYPE StopSound( BSTR Sound) = 0; diff --git a/standalone/vpinball_standalone_i_proxy.cpp b/standalone/vpinball_standalone_i_proxy.cpp index 72a2d2e..2fe1b73 100644 --- a/standalone/vpinball_standalone_i_proxy.cpp +++ b/standalone/vpinball_standalone_i_proxy.cpp @@ -3043,7 +3043,15 @@ STDMETHODIMP ScriptGlobalTable::Invoke(DISPID dispIdMember, REFIID /*riid*/, LCI if (wFlags & DISPATCH_PROPERTYGET) { // line 724: [propget, id(14), helpstring("property GetPlayerHWnd")] HRESULT GetPlayerHWnd([out, retval] SIZE_T *pVal); V_VT(&res) = VT_UI4; + // vpinball-wasm: this generated proxy hardcodes the _WIN64 (SIZE_T) + // overload of get_GetPlayerHWnd; on a genuine 32-bit target (wasm32) + // the interface instead declares the LONG overload (see + // ScriptGlobalTable.h's #ifdef _WIN64), so match it here too. +#ifdef _WIN64 hres = get_GetPlayerHWnd((SIZE_T*)&V_UI4(&res)); +#else + hres = get_GetPlayerHWnd((LONG*)&V_UI4(&res)); +#endif } break; }