diff --git a/.gitignore b/.gitignore index 70be3aea0af..c047abb4767 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ CMakeFiles/ stk-editor/ .vscode/ tags.* +wasm/build/ +wasm/prefix/ +wasm/emsdk/ +wasm/web/game # clangd .cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 010a663f039..27509c251f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,62 @@ if(IOS) include(cmake/XcodeHelper.cmake) endif() +if(EMSCRIPTEN) + #note: cmake fails the first few times you run it then works fine without changing anything + #i have no idea why this happens + + set(CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/wasm/prefix/") + + set(JPEG_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include") + set(JPEG_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libjpeg.a") + + set(PNG_PNG_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(PNG_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libpng16.a") + + set(ZLIB_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include") + set(ZLIB_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libz.a") + + set(HARFBUZZ_INCLUDEDIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(HARFBUZZ_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libharfbuzz.a") + + set(CURL_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(CURL_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libcurl.a") + + set(OPENSSL_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(OPENSSL_CRYPTO_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libssl.a") + + set(OGGVORBIS_OGG_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(OGGVORBIS_VORBIS_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/wasm/prefix/include/") + set(OGGVORBIS_OGG_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libogg.a") + set(OGGVORBIS_VORBISFILE_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libvorbisfile.a") + set(OGGVORBIS_VORBIS_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/prefix/lib/libvorbis.a") + + set(PTHREAD_LIBRARY "${CMAKE_SOURCE_DIR}/wasm/emsdk/upstream") + + set(USE_WIIUSE 0) + set(USE_DNS_C 1) + set(USE_GLES2 1) + set(NO_SHADERC on) + add_definitions(-DNO_IRR_COMPILE_WITH_X11_) + add_definitions(-DNO_IRR_COMPILE_WITH_WAYLAND_DEVICE_) + add_definitions(-DNO_IRR_COMPILE_WITH_DIRECT3D_9_) + add_definitions(-DNO_IRR_COMPILE_WITH_VULKAN_) + add_definitions(-DNO_IRR_COMPILE_WITH_OPENGL_) + add_definitions(-D_IRR_COMPILE_WITH_OGLES2_) + add_definitions(-DDISABLE_RPC) + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sUSE_SDL=2 -fwasm-exceptions -sSUPPORT_LONGJMP=wasm -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sUSE_SDL=2 -fwasm-exceptions -sSUPPORT_LONGJMP=wasm -pthread") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SOURCE_DIR}/wasm/prefix/lib/ -sENVIRONMENT=web,worker -lidbfs.js -lwebsocket.js -sASSERTIONS=1 -sSTACK_SIZE=30000000 -sMIN_WEBGL_VERSION=2 -sFULL_ES2 -sFULL_ES3 -sGL_FFP_ONLY -sUSE_SDL_MIXER=2 -lopenal -sINITIAL_MEMORY=768MB -sWASM_BIGINT -lcrypto -fwasm-exceptions") + + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --profiling") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + endif() + +endif() + if(APPLE AND NOT IOS) if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm") set(ARCHFLAGS "arm64") @@ -331,7 +387,7 @@ else() MESSAGE(STATUS "Use system libmcpp: ${MCPP_LIBRARY}") endif() -if (NOT SERVER_ONLY) +if (NOT SERVER_ONLY AND NOT EMSCRIPTEN) # SDL2 find_library(SDL2_LIBRARY NAMES SDL2 libSDL2) find_path(SDL2_INCLUDEDIR NAMES SDL.h PATH_SUFFIXES SDL2 include/SDL2 include PATHS) @@ -633,7 +689,7 @@ int main() { return 0; } " NO_LIBATOMIC_NEEDED) -if (NOT NO_LIBATOMIC_NEEDED) +if (NOT NO_LIBATOMIC_NEEDED AND NOT EMSCRIPTEN) target_link_libraries(supertuxkart atomic) endif() @@ -866,7 +922,6 @@ if(MINGW) endif() endif() - # Find LibGamerzilla library or build it if missing if (NOT APPLE) include(FindPkgConfig) diff --git a/lib/angelscript/projects/cmake/CMakeLists.txt b/lib/angelscript/projects/cmake/CMakeLists.txt index 42883d5e3ab..324c7874fea 100644 --- a/lib/angelscript/projects/cmake/CMakeLists.txt +++ b/lib/angelscript/projects/cmake/CMakeLists.txt @@ -191,7 +191,9 @@ endif() # Don't override the default library output path to avoid conflicts when building for multiple target platforms #set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../../lib) -target_link_libraries(${ANGELSCRIPT_LIBRARY_NAME} Threads::Threads) +if(NOT EMSCRIPTEN) + target_link_libraries(${ANGELSCRIPT_LIBRARY_NAME} Threads::Threads) +endif() set_target_properties(${ANGELSCRIPT_LIBRARY_NAME} PROPERTIES VERSION ${PROJECT_VERSION}) diff --git a/lib/enet/unix.c b/lib/enet/unix.c index c10a3d99d99..3f4e952b4fc 100644 --- a/lib/enet/unix.c +++ b/lib/enet/unix.c @@ -53,7 +53,7 @@ #include #endif -#ifndef HAS_SOCKLEN_T +#if !defined(HAS_SOCKLEN_T) && !defined(__EMSCRIPTEN__) typedef int socklen_t; #endif diff --git a/lib/graphics_engine/CMakeLists.txt b/lib/graphics_engine/CMakeLists.txt index b42ff383c70..a76f9109a8b 100644 --- a/lib/graphics_engine/CMakeLists.txt +++ b/lib/graphics_engine/CMakeLists.txt @@ -42,10 +42,12 @@ include_directories("${PROJECT_SOURCE_DIR}/lib/graphics_utils") include_directories("${PROJECT_SOURCE_DIR}/lib/irrlicht/include") include_directories("${PROJECT_SOURCE_DIR}/lib/bullet/src") find_path(SDL2_INCLUDEDIR NAMES SDL.h PATH_SUFFIXES SDL2 include/SDL2 include PATHS) -if (NOT SDL2_INCLUDEDIR) - message(FATAL_ERROR "SDL2 not found.") -else() - include_directories("${SDL2_INCLUDEDIR}") +if (NOT EMSCRIPTEN) + if (NOT SDL2_INCLUDEDIR) + message(FATAL_ERROR "SDL2 not found.") + else() + include_directories("${SDL2_INCLUDEDIR}") + endif() endif() if(APPLE AND NOT DLOPEN_MOLTENVK) diff --git a/lib/graphics_engine/include/ge_main.hpp b/lib/graphics_engine/include/ge_main.hpp index 656963c99d9..cecaf98f74f 100644 --- a/lib/graphics_engine/include/ge_main.hpp +++ b/lib/graphics_engine/include/ge_main.hpp @@ -18,7 +18,9 @@ namespace irr namespace GE { +#ifdef _IRR_COMPILE_WITH_VULKAN_ class GEVulkanDriver; +#endif struct GEConfig { bool m_disable_npot_texture; @@ -34,7 +36,9 @@ float m_render_scale; void setVideoDriver(irr::video::IVideoDriver* driver); void setShaderFolder(const std::string& path); irr::video::IVideoDriver* getDriver(); +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* getVKDriver(); +#endif const std::string& getShaderFolder(); GEConfig* getGEConfig(); void deinit(); diff --git a/lib/graphics_engine/src/ge_compressor_astc_4x4.cpp b/lib/graphics_engine/src/ge_compressor_astc_4x4.cpp index 1b84c2175ee..0ff483f8c77 100644 --- a/lib/graphics_engine/src/ge_compressor_astc_4x4.cpp +++ b/lib/graphics_engine/src/ge_compressor_astc_4x4.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_compressor_astc_4x4.hpp" #include "ge_main.hpp" @@ -135,3 +137,5 @@ GECompressorASTC4x4::GECompressorASTC4x4(uint8_t* texture, unsigned channels, } // GECompressorASTC4x4 } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_gl_texture.cpp b/lib/graphics_engine/src/ge_gl_texture.cpp index 1dc95760847..7f26b1ef22d 100644 --- a/lib/graphics_engine/src/ge_gl_texture.cpp +++ b/lib/graphics_engine/src/ge_gl_texture.cpp @@ -60,6 +60,7 @@ GEGLTexture::GEGLTexture(const std::string& name, unsigned int size, m_size = m_orig_size; bool texture_swizzle = false; +#ifndef __EMSCRIPTEN__ if (m_driver_type == video::EDT_OGLES2) { int gl_major_version = 0; @@ -74,6 +75,7 @@ GEGLTexture::GEGLTexture(const std::string& name, unsigned int size, texture_swizzle = irr::video::useCoreContext && GE::hasGLExtension("GL_ARB_texture_swizzle"); } +#endif if (single_channel && texture_swizzle) m_single_channel = true; diff --git a/lib/graphics_engine/src/ge_main.cpp b/lib/graphics_engine/src/ge_main.cpp index 04b02d9b899..63dec2c9687 100644 --- a/lib/graphics_engine/src/ge_main.cpp +++ b/lib/graphics_engine/src/ge_main.cpp @@ -44,10 +44,12 @@ irr::video::IVideoDriver* getDriver() return g_driver; } +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* getVKDriver() { return dynamic_cast(g_driver); } +#endif GEConfig* getGEConfig() { @@ -133,6 +135,8 @@ void mathPlaneFrustumf(float* out, const irr::core::matrix4& pvm) mathPlaneNormf(&out[5 * 4]); } +#ifdef _IRR_COMPILE_WITH_VULKAN_ + irr::scene::IAnimatedMesh* convertIrrlichtMeshToSPM(irr::scene::IMesh* mesh) { GESPM* spm = new GESPM(); @@ -172,5 +176,7 @@ irr::scene::IAnimatedMesh* convertIrrlichtMeshToSPM(irr::scene::IMesh* mesh) spm->finalize(); return spm; } +#endif } + diff --git a/lib/graphics_engine/src/ge_spm_buffer.cpp b/lib/graphics_engine/src/ge_spm_buffer.cpp index 1179a732566..013a6a4c71c 100644 --- a/lib/graphics_engine/src/ge_spm_buffer.cpp +++ b/lib/graphics_engine/src/ge_spm_buffer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_spm_buffer.hpp" #include "ge_main.hpp" @@ -117,3 +119,5 @@ void GESPMBuffer::setTCoords(u32 i, const core::vector2df& tcoords) } // setTCoords } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_texture.cpp b/lib/graphics_engine/src/ge_texture.cpp index 314d266c914..28e96ca75f8 100644 --- a/lib/graphics_engine/src/ge_texture.cpp +++ b/lib/graphics_engine/src/ge_texture.cpp @@ -101,8 +101,10 @@ irr::video::ITexture* createTexture(const std::string& path, case video::EDT_DIRECT3D9: return new GEDX9Texture(path, image_mani); #endif +#ifdef _IRR_COMPILE_WITH_VULKAN_ case video::EDT_VULKAN: return new GEVulkanTexture(path, image_mani); +#endif default: return NULL; } @@ -121,8 +123,10 @@ irr::video::ITexture* createTexture(video::IImage* img, case video::EDT_DIRECT3D9: return new GEDX9Texture(img, name); #endif +#ifdef _IRR_COMPILE_WITH_VULKAN_ case video::EDT_VULKAN: return new GEVulkanTexture(img, name); +#endif default: return NULL; } @@ -141,8 +145,10 @@ irr::video::ITexture* createFontTexture(const std::string& name, case video::EDT_DIRECT3D9: return new GEDX9Texture(name, size); #endif +#ifdef _IRR_COMPILE_WITH_VULKAN_ case video::EDT_VULKAN: return new GEVulkanTexture(name, size, single_channel); +#endif default: return NULL; } diff --git a/lib/graphics_engine/src/ge_vulkan_2d_renderer.cpp b/lib/graphics_engine/src/ge_vulkan_2d_renderer.cpp index 771169eed66..4953b2f83cc 100644 --- a/lib/graphics_engine/src/ge_vulkan_2d_renderer.cpp +++ b/lib/graphics_engine/src/ge_vulkan_2d_renderer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_2d_renderer.hpp" #include "ge_main.hpp" @@ -439,3 +441,5 @@ void GEVulkan2dRenderer::addVerticesIndices(irr::video::S3DVertex* vertices, } // addVerticesIndices } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_array_texture.cpp b/lib/graphics_engine/src/ge_vulkan_array_texture.cpp index 0f817dc40fd..d37d1dd0884 100644 --- a/lib/graphics_engine/src/ge_vulkan_array_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_array_texture.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_array_texture.hpp" #include "ge_main.hpp" @@ -259,3 +261,5 @@ void GEVulkanArrayTexture::reloadInternal(const std::vector& list, } // reloadInternal } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_billboard_buffer.cpp b/lib/graphics_engine/src/ge_vulkan_billboard_buffer.cpp index 2ed6653a9c8..1b3434e3eec 100644 --- a/lib/graphics_engine/src/ge_vulkan_billboard_buffer.cpp +++ b/lib/graphics_engine/src/ge_vulkan_billboard_buffer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_billboard_buffer.hpp" #include "ge_main.hpp" @@ -15,3 +17,5 @@ GEVulkanBillboardBuffer::GEVulkanBillboardBuffer( } // GEVulkanBillboardBuffer } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_camera_scene_node.cpp b/lib/graphics_engine/src/ge_vulkan_camera_scene_node.cpp index a5b17a96921..b8816483dbf 100644 --- a/lib/graphics_engine/src/ge_vulkan_camera_scene_node.cpp +++ b/lib/graphics_engine/src/ge_vulkan_camera_scene_node.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_camera_scene_node.hpp" #include "ge_main.hpp" @@ -68,3 +70,5 @@ irr::core::matrix4 GEVulkanCameraSceneNode::getPVM() const } // getPVM } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_command_loader.cpp b/lib/graphics_engine/src/ge_vulkan_command_loader.cpp index 13cadd63daf..f5209d72fb2 100644 --- a/lib/graphics_engine/src/ge_vulkan_command_loader.cpp +++ b/lib/graphics_engine/src/ge_vulkan_command_loader.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_command_loader.hpp" #include "ge_vulkan_driver.hpp" @@ -264,3 +266,5 @@ void GEVulkanCommandLoader::waitIdle() } // waitIdle } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_depth_texture.cpp b/lib/graphics_engine/src/ge_vulkan_depth_texture.cpp index 0411c617df6..930c87c4c9a 100644 --- a/lib/graphics_engine/src/ge_vulkan_depth_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_depth_texture.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include #include "ge_vulkan_depth_texture.hpp" @@ -39,3 +41,5 @@ GEVulkanDepthTexture::GEVulkanDepthTexture(GEVulkanDriver* vk, } // GEVulkanDepthTexture } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_draw_call.cpp b/lib/graphics_engine/src/ge_vulkan_draw_call.cpp index 3eb37616772..0b647e1ce3c 100644 --- a/lib/graphics_engine/src/ge_vulkan_draw_call.cpp +++ b/lib/graphics_engine/src/ge_vulkan_draw_call.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_draw_call.hpp" #include "ge_culling_tool.hpp" @@ -1769,3 +1771,5 @@ void GEVulkanDrawCall::updateDataDescriptorSets(GEVulkanDriver* vk) } } // updateDataDescriptor } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_driver.cpp b/lib/graphics_engine/src/ge_vulkan_driver.cpp index 6ae8f7202d8..94a159414d7 100644 --- a/lib/graphics_engine/src/ge_vulkan_driver.cpp +++ b/lib/graphics_engine/src/ge_vulkan_driver.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_driver.hpp" #include "ge_compressor_astc_4x4.hpp" @@ -2641,3 +2643,5 @@ extern "C" JNIEXPORT void JNICALL pauseRenderingJNI(JNIEnv* env, jclass cls) #endif #endif + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_dynamic_buffer.cpp b/lib/graphics_engine/src/ge_vulkan_dynamic_buffer.cpp index 9d8cb076c04..2fd7debde53 100644 --- a/lib/graphics_engine/src/ge_vulkan_dynamic_buffer.cpp +++ b/lib/graphics_engine/src/ge_vulkan_dynamic_buffer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_dynamic_buffer.hpp" #include "ge_vulkan_driver.hpp" @@ -222,3 +224,5 @@ VkBuffer GEVulkanDynamicBuffer::getCurrentBuffer() const } // getCurrentBuffer } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_dynamic_spm_buffer.cpp b/lib/graphics_engine/src/ge_vulkan_dynamic_spm_buffer.cpp index 829727eeb38..18ec4a72a07 100644 --- a/lib/graphics_engine/src/ge_vulkan_dynamic_spm_buffer.cpp +++ b/lib/graphics_engine/src/ge_vulkan_dynamic_spm_buffer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_dynamic_spm_buffer.hpp" #include "ge_main.hpp" @@ -130,3 +132,5 @@ void GEVulkanDynamicSPMBuffer::setDirtyOffset(irr::u32 offset, } // setDirtyOffset } // end namespace GE + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_fbo_texture.cpp b/lib/graphics_engine/src/ge_vulkan_fbo_texture.cpp index 834a0b43b1e..5abff834788 100644 --- a/lib/graphics_engine/src/ge_vulkan_fbo_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_fbo_texture.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_fbo_texture.hpp" #include "ge_main.hpp" @@ -151,3 +153,5 @@ void GEVulkanFBOTexture::createRTT() } // createRTT } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_features.cpp b/lib/graphics_engine/src/ge_vulkan_features.cpp index ec3dabd7543..ce227d1568d 100644 --- a/lib/graphics_engine/src/ge_vulkan_features.cpp +++ b/lib/graphics_engine/src/ge_vulkan_features.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_features.hpp" #include "ge_compressor_astc_4x4.hpp" @@ -340,3 +342,5 @@ bool GEVulkanFeatures::supportsASTC4x4() } // supportsASTC4x4 } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_mesh_cache.cpp b/lib/graphics_engine/src/ge_vulkan_mesh_cache.cpp index a821ee4a962..02c6b3661cf 100644 --- a/lib/graphics_engine/src/ge_vulkan_mesh_cache.cpp +++ b/lib/graphics_engine/src/ge_vulkan_mesh_cache.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_mesh_cache.hpp" #include "ge_main.hpp" @@ -196,3 +198,5 @@ void GEVulkanMeshCache::destroy() } // destroy } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_scene_manager.cpp b/lib/graphics_engine/src/ge_vulkan_scene_manager.cpp index 36b8c97545a..cb56a79dccc 100644 --- a/lib/graphics_engine/src/ge_vulkan_scene_manager.cpp +++ b/lib/graphics_engine/src/ge_vulkan_scene_manager.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_scene_manager.hpp" #include "../source/Irrlicht/os.h" @@ -274,3 +276,5 @@ void GEVulkanSceneManager::removeDrawCall(GEVulkanCameraSceneNode* cam) } // removeDrawCall } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_shader_manager.cpp b/lib/graphics_engine/src/ge_vulkan_shader_manager.cpp index 9ddcd68b805..66462cf08d5 100644 --- a/lib/graphics_engine/src/ge_vulkan_shader_manager.cpp +++ b/lib/graphics_engine/src/ge_vulkan_shader_manager.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_shader_manager.hpp" #include "ge_vulkan_command_loader.hpp" @@ -285,3 +287,5 @@ VkShaderModule GEVulkanShaderManager::getShader(const std::string& filename) } // getShader } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_skybox_renderer.cpp b/lib/graphics_engine/src/ge_vulkan_skybox_renderer.cpp index 82977f00785..bf509ac3313 100644 --- a/lib/graphics_engine/src/ge_vulkan_skybox_renderer.cpp +++ b/lib/graphics_engine/src/ge_vulkan_skybox_renderer.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_skybox_renderer.hpp" #include "ge_main.hpp" @@ -385,3 +387,5 @@ void GEVulkanSkyBoxRenderer::addSkyBox(GEVulkanCameraSceneNode* cam, } // addSkyBox } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_texture.cpp b/lib/graphics_engine/src/ge_vulkan_texture.cpp index c3d977ec7da..c7bdf429cdd 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_texture.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_texture.hpp" #include "ge_main.hpp" @@ -921,3 +923,5 @@ std::shared_ptr > GEVulkanTexture::getImageViewLive( } // getImageViewLive } + +#endif \ No newline at end of file diff --git a/lib/graphics_engine/src/ge_vulkan_texture_descriptor.cpp b/lib/graphics_engine/src/ge_vulkan_texture_descriptor.cpp index 84eb331bd67..6b58daf24d9 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture_descriptor.cpp +++ b/lib/graphics_engine/src/ge_vulkan_texture_descriptor.cpp @@ -1,3 +1,5 @@ +#ifdef _IRR_COMPILE_WITH_VULKAN_ + #include "ge_vulkan_texture_descriptor.hpp" #include "ge_main.hpp" @@ -237,3 +239,5 @@ int GEVulkanTextureDescriptor::getTextureID(const irr::video::ITexture** list, } // getTextureID } + +#endif \ No newline at end of file diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceSDL.cpp b/lib/irrlicht/source/Irrlicht/CIrrDeviceSDL.cpp index 406884d06cd..22937ec994c 100644 --- a/lib/irrlicht/source/Irrlicht/CIrrDeviceSDL.cpp +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceSDL.cpp @@ -23,11 +23,13 @@ #include "guiengine/engine.hpp" #include "ge_main.hpp" #include "glad/gl.h" + +#ifdef _IRR_COMPILE_WITH_VULKAN_ #include "ge_vulkan_driver.hpp" #include "ge_vulkan_scene_manager.hpp" #include "MoltenVK.h" - #include +#endif extern bool GLContextDebugBit; @@ -107,7 +109,7 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters& param) } #endif - u32 init_flags = SDL_INIT_TIMER | SDL_INIT_VIDEO; + u32 init_flags = SDL_INIT_VIDEO; if (SDL_Init(init_flags) < 0) { os::Printer::log("Unable to initialize SDL!", SDL_GetError()); @@ -212,7 +214,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters& param) #endif } - void CIrrDeviceSDL::updateNativeScale(u32* saving_width, u32* saving_height) { int width, height = 0; @@ -224,10 +225,12 @@ void CIrrDeviceSDL::updateNativeScale(u32* saving_width, u32* saving_height) { SDL_GL_GetDrawableSize(Window, &real_width, &real_height); } +#ifdef _IRR_COMPILE_WITH_VULKAN_ else if (CreationParams.DriverType == video::EDT_VULKAN) { SDL_Vulkan_GetDrawableSize(Window, &real_width, &real_height); } +#endif NativeScaleX = (f32)real_width / (f32)width; NativeScaleY = (f32)real_height / (f32)height; if (saving_width) @@ -236,7 +239,6 @@ void CIrrDeviceSDL::updateNativeScale(u32* saving_width, u32* saving_height) *saving_height = height; } - //! destructor CIrrDeviceSDL::~CIrrDeviceSDL() { @@ -254,11 +256,13 @@ CIrrDeviceSDL::~CIrrDeviceSDL() es2->cleanUp(); } #endif +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* gevk = dynamic_cast(VideoDriver); if (gevk) gevk->destroyVulkan(); VideoDriver->drop(); VideoDriver = NULL; +#endif } #ifdef DLOPEN_MOLTENVK delete m_moltenvk; @@ -373,6 +377,7 @@ bool versionCorrect(int major, int minor) // Used in OptionsScreenVideo for live fullscreen toggle for vulkan driver extern "C" void update_fullscreen_desktop(int val) { +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* gevk = GE::getVKDriver(); if (!gevk || !GE::getGEConfig()->m_fullscreen_desktop) return; @@ -390,6 +395,9 @@ extern "C" void update_fullscreen_desktop(int val) SDL_SetWindowSize(window, prev_width * 0.8f, prev_height * 0.8f); SDL_RaiseWindow(window); } +#else + return; +#endif } @@ -401,12 +409,14 @@ extern "C" void update_swap_interval(int swap_interval) if (swap_interval > 1) swap_interval = 1; +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* gevk = GE::getVKDriver(); if (gevk) { gevk->updateSwapInterval(swap_interval); return; } +#endif // Try adaptive vsync first if support if (swap_interval > 0) @@ -459,7 +469,7 @@ bool CIrrDeviceSDL::createWindow() #endif if (CreationParams.Fullscreen) - { + { if (GE::getGEConfig()->m_fullscreen_desktop) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; @@ -485,7 +495,9 @@ bool CIrrDeviceSDL::createWindow() #if SDL_VERSION_ATLEAST(2, 0, 12) SDL_SetHint(SDL_HINT_VIDEO_EXTERNAL_CONTEXT, "1"); #endif +#ifdef _IRR_COMPILE_WITH_VULKAN_ flags |= SDL_WINDOW_VULKAN; +#endif } #ifdef MOBILE_STK @@ -501,7 +513,9 @@ bool CIrrDeviceSDL::createWindow() os::Printer::log( "Could not initialize display!" ); return false; } +#ifndef __EMSCRIPTEN__ update_swap_interval(CreationParams.SwapInterval); +#endif } else { @@ -530,15 +544,19 @@ void CIrrDeviceSDL::tryCreateOpenGLContext(u32 flags) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, CreationParams.Doublebuffer); irr::video::useCoreContext = true; +#ifndef __EMSCRIPTEN__ if (GLContextDebugBit) SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); +#endif if (CreationParams.DriverType == video::EDT_OGLES2) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); else SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); +#ifndef __EMSCRIPTEN__ if (CreationParams.ForceLegacyDevice) +#endif goto legacy; #ifdef _IRR_COMPILE_WITH_OGLES2_ @@ -1694,8 +1712,10 @@ void CIrrDeviceSDL::createGUIAndVulkanScene() GUIEnvironment = gui::createGUIEnvironment(FileSystem, VideoDriver, Operator); #endif + #ifdef _IRR_COMPILE_WITH_VULKAN_ // create Scene manager SceneManager = new GE::GEVulkanSceneManager(VideoDriver, FileSystem, CursorControl, GUIEnvironment); + #endif setEventReceiver(UserReceiver); } diff --git a/lib/irrlicht/source/Irrlicht/COGLES2Driver.cpp b/lib/irrlicht/source/Irrlicht/COGLES2Driver.cpp index 68ee9a4dd9c..4e358f64903 100644 --- a/lib/irrlicht/source/Irrlicht/COGLES2Driver.cpp +++ b/lib/irrlicht/source/Irrlicht/COGLES2Driver.cpp @@ -82,6 +82,9 @@ namespace video egl_params.platform = CEGL_PLATFORM_DEFAULT; egl_params.window = ((struct android_app *)(params.PrivateData))->window; egl_params.display = NULL; +#elif defined(__EMSCRIPTEN__) + egl_params.platform = CEGL_PLATFORM_DEFAULT; + egl_params.display = NULL; #endif EglContext->init(egl_params); diff --git a/lib/irrlicht/source/Irrlicht/CWriteFile.cpp b/lib/irrlicht/source/Irrlicht/CWriteFile.cpp index bcd5c5d720c..cccc3384695 100644 --- a/lib/irrlicht/source/Irrlicht/CWriteFile.cpp +++ b/lib/irrlicht/source/Irrlicht/CWriteFile.cpp @@ -7,6 +7,11 @@ #include "utils/file_utils.hpp" #include + +#if defined(__EMSCRIPTEN__) +#include +#endif + namespace irr { namespace io @@ -48,7 +53,13 @@ s32 CWriteFile::write(const void* buffer, u32 sizeToWrite) if (!isOpen()) return 0; - return (s32)fwrite(buffer, 1, sizeToWrite, File); + s32 ret = fwrite(buffer, 1, sizeToWrite, File); +#ifdef __EMSCRIPTEN__ + EM_ASM( + globalThis.sync_idbfs(); + ); +#endif + return ret; } diff --git a/src/audio/sfx_manager.cpp b/src/audio/sfx_manager.cpp index 66aeb38b69f..26e5cb9585c 100644 --- a/src/audio/sfx_manager.cpp +++ b/src/audio/sfx_manager.cpp @@ -95,7 +95,7 @@ SFXManager::SFXManager() { // The thread is created even if there atm sfx are disabled // (since the user might enable it later). -#ifndef __SWITCH__ +#if !defined(__SWITCH__) && !defined(__EMSCRIPTEN__) m_thread = std::thread(std::bind(mainLoop, this)); #endif setMasterSFXVolume( UserConfigParams::m_sfx_volume ); @@ -111,7 +111,7 @@ SFXManager::SFXManager() */ SFXManager::~SFXManager() { -#if defined(ENABLE_SOUND) && !defined(__SWITCH__) +#if defined(ENABLE_SOUND) && !defined(__SWITCH__) && !defined(__EMSCRIPTEN__) if (UserConfigParams::m_enable_sound) { m_thread.join(); @@ -348,19 +348,19 @@ void SFXManager::mainLoop(void *obj) if (!UserConfigParams::m_enable_sound) return; -#ifndef __SWITCH__ +#if !defined(__SWITCH__) && !defined(__EMSCRIPTEN__) VS::setThreadName("SFXManager"); #endif SFXManager *me = (SFXManager*)obj; std::unique_lock ul = me->m_sfx_commands.acquireMutex(); -#ifdef __SWITCH__ +#if defined(__SWITCH__) || defined(__EMSCRIPTEN__) int iterCount = 0; #endif // Wait till we have an empty sfx in the queue while ( -#ifdef __SWITCH__ +#if defined(__SWITCH__) || defined(__EMSCRIPTEN__) // Don't spend too much time working on audio ++iterCount != 30 && !me->m_sfx_commands.getData().empty() #else @@ -374,7 +374,7 @@ void SFXManager::mainLoop(void *obj) // Wait in cond_wait for a request to arrive. The 'while' is necessary // since "spurious wakeups from the pthread_cond_wait ... may occur" // (pthread_cond_wait man page)! -#ifndef __SWITCH__ +#if !defined(__SWITCH__) && !defined(__EMSCRIPTEN__) while (empty) { me->m_condition_variable.wait(ul); @@ -387,7 +387,7 @@ void SFXManager::mainLoop(void *obj) if (current->m_command == SFX_EXIT) { delete current; -#ifdef __SWITCH__ +#if defined(__SWITCH__) || defined(__EMSCRIPTEN__) return; #else break; @@ -464,6 +464,7 @@ void SFXManager::mainLoop(void *obj) current = NULL; PROFILER_POP_CPU_MARKER(); PROFILER_PUSH_CPU_MARKER("yield", 0, 0, 255); +#ifndef __EMSCRIPTEN__ if (empty_queue && me->sfxAllowed()) { // Wait some time to let other threads run, then queue an @@ -473,6 +474,7 @@ void SFXManager::mainLoop(void *obj) t = StkTime::getMonoTimeMs() - t; me->queue(SFX_UPDATE, (SFXBase*)NULL, float(t / 1000.0)); } +#endif ul = me->m_sfx_commands.acquireMutex(); PROFILER_POP_CPU_MARKER(); } // while @@ -482,7 +484,7 @@ void SFXManager::mainLoop(void *obj) // need to keep the user waiting for STK to exit. me->setCanBeDeleted(); -#ifndef __SWITCH__ +#if !defined(__SWITCH__) && !defined(__EMSCRIPTEN__) // Clean up memory to avoid leak detection while(!me->m_sfx_commands.getData().empty()) { @@ -808,7 +810,7 @@ void SFXManager::update() // Wake up the sfx thread to handle all queued up audio commands. m_condition_variable.notify_one(); -#ifdef __SWITCH__ +#if defined(__SWITCH__) || defined(__EMSCRIPTEN__) mainLoop(this); #endif #endif diff --git a/src/config/user_config.cpp b/src/config/user_config.cpp index 31df64bae01..befcdfeda36 100644 --- a/src/config/user_config.cpp +++ b/src/config/user_config.cpp @@ -50,6 +50,10 @@ static std::vector all_params; #include #include +#if defined(__EMSCRIPTEN__) +#include +#endif + const int UserConfig::m_current_config_version = 8; @@ -749,6 +753,12 @@ void UserConfig::saveConfig() configfile.close(); file_manager->removeFile(filename); FileUtils::renameU8Path(filename + "new", filename); + +#ifdef __EMSCRIPTEN__ + EM_ASM( + globalThis.sync_idbfs(); + ); +#endif } catch (std::runtime_error& e) { diff --git a/src/graphics/b3d_mesh_loader.cpp b/src/graphics/b3d_mesh_loader.cpp index 38a50ae6ecf..7df84d1d537 100644 --- a/src/graphics/b3d_mesh_loader.cpp +++ b/src/graphics/b3d_mesh_loader.cpp @@ -82,7 +82,7 @@ scene::IAnimatedMesh* B3DMeshLoader::createMesh(io::IReadFile* f) AnimatedMesh->setMinMax(min.toIrrVector(), max.toIrrVector()); } -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) bool convert_spm = CVS->isGLSL() || GE::getVKDriver() != NULL; if (convert_spm) { @@ -1371,7 +1371,7 @@ void B3DMeshLoader::loadTextures(SB3dMaterial& material, scene::IMeshBuffer* mb) } #endif -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) bool convert_spm = CVS->isGLSL() || GE::getVKDriver() != NULL; if (convert_spm) { diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 5e353fa87a3..00950ae2453 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -75,12 +75,14 @@ void CentralVideoSettings::init() GE::getGEConfig()->m_disable_npot_texture = GraphicsRestrictions::isDisabled( GraphicsRestrictions::GR_NPOT_TEXTURES); + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (GE::getDriver()->getDriverType() == video::EDT_VULKAN) { hasTextureCompression = GEVulkanFeatures::supportsS3TCBC3() || GEVulkanFeatures::supportsBPTCBC7() || GEVulkanFeatures::supportsASTC4x4(); } + #endif return; } diff --git a/src/graphics/cpu_particle_manager.cpp b/src/graphics/cpu_particle_manager.cpp index 765c9a8d856..81485f35209 100644 --- a/src/graphics/cpu_particle_manager.cpp +++ b/src/graphics/cpu_particle_manager.cpp @@ -304,9 +304,14 @@ void CPUParticleManager::uploadAll() glBindBuffer(GL_ARRAY_BUFFER, 0); continue; } +#ifdef __EMSCRIPTEN__ + void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, vbo_size * 20, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#else void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, vbo_size * 20, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#endif memcpy(ptr, m_particles_generated[p.first].data(), vbo_size * 20); glUnmapBuffer(GL_ARRAY_BUFFER); glBindBuffer(GL_ARRAY_BUFFER, 0); diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index da4fc1aea84..a437ebd1871 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -649,6 +649,7 @@ void IrrDriver::initDevice() GE::setVideoDriver(m_device->getVideoDriver()); +#ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* vk = GE::getVKDriver(); if (vk) { @@ -671,6 +672,7 @@ void IrrDriver::initDevice() break; } } +#endif // Assume sp is supported CentralVideoSettings::m_supports_sp = true; CVS->init(); @@ -1403,13 +1405,13 @@ scene::ISceneNode *IrrDriver::addSphere(float radius, } #endif -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) bool vk = (GE::getVKDriver() != NULL); if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = true; #endif scene::IMeshSceneNode *node = m_scene_manager->addMeshSceneNode(mesh); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = false; #endif diff --git a/src/graphics/material.cpp b/src/graphics/material.cpp index c50b08dff12..5db71cd1586 100644 --- a/src/graphics/material.cpp +++ b/src/graphics/material.cpp @@ -864,7 +864,7 @@ void Material::setMaterialProperties(video::SMaterial *m, scene::IMeshBuffer* m tc.X = 1 - tc.X; } } -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (is_vk) { GE::GESPMBuffer* spmb = static_cast(mb); diff --git a/src/graphics/shader_based_renderer.cpp b/src/graphics/shader_based_renderer.cpp index 943b8348fff..8bced92aba7 100644 --- a/src/graphics/shader_based_renderer.cpp +++ b/src/graphics/shader_based_renderer.cpp @@ -227,7 +227,6 @@ void ShaderBasedRenderer::renderSceneDeferred(scene::ICameraSceneNode * const ca bool hasShadow, bool forceRTT) { - if (CVS->isARBUniformBufferObjectUsable()) { glBindBufferBase(GL_UNIFORM_BUFFER, 0, diff --git a/src/graphics/shadow.cpp b/src/graphics/shadow.cpp index 5ccaf7aeedd..047c4dfb3c1 100644 --- a/src/graphics/shadow.cpp +++ b/src/graphics/shadow.cpp @@ -64,6 +64,7 @@ Shadow::Shadow(Material* shadow_mat, const AbstractKart& kart) { std::array indices = {{ 0, 1, 2, 0, 2, 3 }}; scene::IMeshBuffer* buffer = NULL; + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (irr_driver->getVideoDriver()->getDriverType() == video::EDT_VULKAN) { buffer = new GE::GEVulkanDynamicSPMBuffer(); @@ -74,6 +75,9 @@ Shadow::Shadow(Material* shadow_mat, const AbstractKart& kart) buffer->append(vertices.data(), vertices.size(), indices.data(), indices.size()); } + #else + if (false) {} + #endif else { buffer = new scene::SMeshBuffer(); diff --git a/src/graphics/skid_marks.cpp b/src/graphics/skid_marks.cpp index f7d45d66964..ec5f1713799 100644 --- a/src/graphics/skid_marks.cpp +++ b/src/graphics/skid_marks.cpp @@ -253,9 +253,11 @@ SkidMarks::SkidMarkQuads::SkidMarkQuads(const Vec3 &left, else { scene::IMeshBuffer* buffer = NULL; +#ifdef _IRR_COMPILE_WITH_VULKAN_ if (irr_driver->getVideoDriver()->getDriverType() == video::EDT_VULKAN) buffer = new GE::GEVulkanDynamicSPMBuffer(); else +#endif buffer = new scene::SMeshBuffer(); material->setMaterialProperties(&buffer->getMaterial(), buffer); buffer->getMaterial().setTexture(0, material->getTexture()); diff --git a/src/graphics/slip_stream.cpp b/src/graphics/slip_stream.cpp index 55c91d19e53..b66e11449fe 100644 --- a/src/graphics/slip_stream.cpp +++ b/src/graphics/slip_stream.cpp @@ -399,7 +399,7 @@ scene::IAnimatedMesh* SlipStream::createMesh(unsigned material_id, mesh->recalculateBoundingBox(); buffer->drop(); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (GE::getDriver()->getDriverType() == video::EDT_VULKAN) { amesh = GE::convertIrrlichtMeshToSPM(mesh); diff --git a/src/graphics/sp/sp_mesh_buffer.cpp b/src/graphics/sp/sp_mesh_buffer.cpp index 1bcd811ed60..590596479a0 100644 --- a/src/graphics/sp/sp_mesh_buffer.cpp +++ b/src/graphics/sp/sp_mesh_buffer.cpp @@ -158,9 +158,14 @@ void SPMeshBuffer::uploadGLMesh() unsigned v_size = (unsigned)m_vertices.size() * pitch; glBufferData(GL_ARRAY_BUFFER, v_size, NULL, GL_DYNAMIC_DRAW); size_t offset = 0; +#ifdef __EMSCRIPTEN__ + char* ptr = (char*)glMapBufferRange(GL_ARRAY_BUFFER, 0, v_size, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#else char* ptr = (char*)glMapBufferRange(GL_ARRAY_BUFFER, 0, v_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#endif v_size = 0; for (unsigned i = 0 ; i < m_vertices.size(); i++) { @@ -407,9 +412,15 @@ void SPMeshBuffer::uploadInstanceData() else { glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]); +#ifdef __EMSCRIPTEN__ + void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, + m_ins_dat[i].size() * 44, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#else + void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, m_ins_dat[i].size() * 44, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +#endif memcpy(ptr, m_ins_dat[i].data(), m_ins_dat[i].size() * 44); glUnmapBuffer(GL_ARRAY_BUFFER); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -474,7 +485,7 @@ void SPMeshBuffer::reloadTextureCompare() void SPMeshBuffer::setSTKMaterial(Material* m) { m_stk_material[0] = std::make_tuple(0u, getIndexCount(), m); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) // Used by b3d mesh loader, clean up later after SP is removed if (GE::getVKDriver() != NULL) return; diff --git a/src/graphics/sp/sp_texture_manager.cpp b/src/graphics/sp/sp_texture_manager.cpp index 307885f858c..4811b962a50 100644 --- a/src/graphics/sp/sp_texture_manager.cpp +++ b/src/graphics/sp/sp_texture_manager.cpp @@ -42,6 +42,12 @@ SPTextureManager::SPTextureManager() { m_max_threaded_load_obj.store(2); } +#ifdef __EMSCRIPTEN__ + //some browsers limit the number of web workers + if (m_max_threaded_load_obj.load() > 8) { + m_max_threaded_load_obj.store(8); + } +#endif m_max_threaded_load_obj.store(m_max_threaded_load_obj.load() + 1); for (unsigned i = 0; i < m_max_threaded_load_obj; i++) { diff --git a/src/graphics/sp_mesh_loader.cpp b/src/graphics/sp_mesh_loader.cpp index decc7f1cc77..8a774caf12b 100644 --- a/src/graphics/sp_mesh_loader.cpp +++ b/src/graphics/sp_mesh_loader.cpp @@ -70,7 +70,7 @@ scene::IAnimatedMesh* SPMeshLoader::createMesh(io::IReadFile* f) m_frame_count = 0; m_mesh = NULL; bool ge_spm = false; -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (GE::getVKDriver()) { ge_spm = true; @@ -448,7 +448,7 @@ void SPMeshLoader::decompressGESPM(irr::io::IReadFile* spm, bool uv_one, bool uv_two, SPVertexType vt, const video::SMaterial& m) { -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) assert(vertices_count != 0); assert(indices_count != 0); diff --git a/src/graphics/stk_tex_manager.cpp b/src/graphics/stk_tex_manager.cpp index 348aacf72b6..b0b6a33cb7a 100644 --- a/src/graphics/stk_tex_manager.cpp +++ b/src/graphics/stk_tex_manager.cpp @@ -37,7 +37,7 @@ // ---------------------------------------------------------------------------- STKTexManager::~STKTexManager() { -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) GE::GEVulkanDriver* gevd = GE::getVKDriver(); if (gevd) { @@ -48,7 +48,7 @@ STKTexManager::~STKTexManager() removeTexture(NULL/*texture*/, true/*remove_all*/); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (gevd) gevd->setDisableWaitIdle(false); #endif @@ -249,7 +249,7 @@ bool STKTexManager::hasTexture(const std::string& path) // ---------------------------------------------------------------------------- void STKTexManager::reloadAllTextures() { -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) GE::GEVulkanDriver* gevd = GE::getVKDriver(); if (gevd) { @@ -265,7 +265,7 @@ void STKTexManager::reloadAllTextures() p.second->reload(); } -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (gevd) gevd->setDisableWaitIdle(false); #endif diff --git a/src/graphics/stk_text_billboard.cpp b/src/graphics/stk_text_billboard.cpp index 2d8fde6b3e4..75a8d6fe719 100644 --- a/src/graphics/stk_text_billboard.cpp +++ b/src/graphics/stk_text_billboard.cpp @@ -381,6 +381,7 @@ void STKTextBillboard::initLegacy(const core::stringw& text, FontWithFace* face) if (SceneManager->getVideoDriver()->getDriverType() == video::EDT_VULKAN) { + #ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GESPM* spm = new GE::GESPM(); for (auto& p : irr_tbs) { @@ -420,6 +421,7 @@ void STKTextBillboard::initLegacy(const core::stringw& text, FontWithFace* face) SceneManager->getMeshCache()->addMesh(oss.str().c_str(), spm); spm->drop(); m_ge_node = SceneManager->addMeshSceneNode(spm, this); + #endif } else { diff --git a/src/input/input_manager.cpp b/src/input/input_manager.cpp index 99cff967f5a..fc57a7d0c2e 100644 --- a/src/input/input_manager.cpp +++ b/src/input/input_manager.cpp @@ -124,11 +124,13 @@ InputManager::InputManager() : m_mode(BOOTSTRAP), SDL_GetError()); } +#ifndef __EMSCRIPTEN__ if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) { Log::error("InputManager", "Failed to init SDL haptics: %s", SDL_GetError()); } +#endif #endif // SERVER_ONLY } diff --git a/src/io/file_manager.cpp b/src/io/file_manager.cpp index ece404d2385..c32ac869dba 100644 --- a/src/io/file_manager.cpp +++ b/src/io/file_manager.cpp @@ -213,6 +213,10 @@ FileManager::FileManager() #endif } +#ifdef __EMSCRIPTEN__ + root_dir = "/data/"; +#endif + if (!m_file_system->existFile((root_dir + version).c_str())) { Log::error("FileManager", "Could not find file '%s'in any " diff --git a/src/items/rubber_band.cpp b/src/items/rubber_band.cpp index 3e137c7361f..3bbb603c646 100644 --- a/src/items/rubber_band.cpp +++ b/src/items/rubber_band.cpp @@ -86,6 +86,7 @@ RubberBand::RubberBand(Plunger *plunger, AbstractKart *kart) scene::IMeshBuffer* buffer = NULL; if (irr_driver->getVideoDriver()->getDriverType() == video::EDT_VULKAN) { + #ifdef _IRR_COMPILE_WITH_VULKAN_ buffer = new GE::GEVulkanDynamicSPMBuffer(); video::S3DVertexSkinnedMesh v; v.m_normal = 0x1FF << 10; @@ -94,6 +95,7 @@ RubberBand::RubberBand(Plunger *plunger, AbstractKart *kart) {{ v, v, v, v }}; buffer->append(vertices.data(), vertices.size(), indices.data(), indices.size()); + #endif } else { diff --git a/src/karts/controller/local_player_controller.cpp b/src/karts/controller/local_player_controller.cpp index 87cacd5e050..0c9a7047ecc 100644 --- a/src/karts/controller/local_player_controller.cpp +++ b/src/karts/controller/local_player_controller.cpp @@ -487,7 +487,7 @@ void LocalPlayerController::doCrashHaptics() { } void LocalPlayerController::rumble(float strength_low, float strength_high, uint16_t duration) { -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && !defined(__EMSCRIPTEN__) if (RewindManager::get()->isRewinding()) return; int count = input_manager->getGamepadCount(); diff --git a/src/karts/kart_properties_manager.cpp b/src/karts/kart_properties_manager.cpp index adce7c117f1..d3c29912725 100644 --- a/src/karts/kart_properties_manager.cpp +++ b/src/karts/kart_properties_manager.cpp @@ -26,6 +26,7 @@ #include "config/user_config.hpp" #include "graphics/irr_driver.hpp" #include "guiengine/engine.hpp" +#include #include "io/file_manager.hpp" #include "karts/kart_properties.hpp" #include "karts/xml_characteristic.hpp" @@ -724,12 +725,14 @@ void KartPropertiesManager::onDemandLoadKartTextures( #ifndef SERVER_ONLY if (STKProcess::getType() != PT_MAIN || kart_list.empty()) return; - + + #ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* gevd = GE::getVKDriver(); if (!gevd) return; gevd->waitIdle(); gevd->setDisableWaitIdle(true); + #endif std::set karts_folder; for (auto& dir : m_kart_search_path) @@ -788,9 +791,11 @@ void KartPropertiesManager::onDemandLoadKartTextures( } } + #ifdef _IRR_COMPILE_WITH_VULKAN_ gevd->setDisableWaitIdle(false); if (unloaded_unused) gevd->handleDeletedTextures(); + #endif #endif } // onDemandLoadKartTextures diff --git a/src/main.cpp b/src/main.cpp index 9d143f78884..0fdc21f78b1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -166,6 +166,10 @@ #include #endif +#ifdef __EMSCRIPTEN__ +#include +#endif + #ifdef __SWITCH__ extern "C" { #include @@ -2109,6 +2113,7 @@ void debugLoop() #endif // ---------------------------------------------------------------------------- +void endMainLoop(); #if defined(ANDROID) int android_main(int argc, char *argv[]) #elif defined(IOS_STK) @@ -2452,7 +2457,7 @@ int main(int argc, char *argv[]) } else if (!CVS->isGLSL()) { - #if !defined(MOBILE_STK) + #if !defined(MOBILE_STK) && !defined(__EMSCRIPTEN__) if (UserConfigParams::m_old_driver_popup) { #ifdef USE_GLES2 @@ -2587,8 +2592,19 @@ int main(int argc, char *argv[]) // Game loaded, bring CPU / GPU clock back to normal appletSetCpuBoostMode(ApmCpuBoostMode_Normal); #endif - +#ifdef __EMSCRIPTEN__ + auto mainLoopWrapper = []() { + main_loop->run(); + if (main_loop->isAborted()) { + endMainLoop(); + emscripten_cancel_main_loop(); + exit(0); + } + }; + emscripten_set_main_loop(mainLoopWrapper, 0, 1); +#else main_loop->run(); +#endif } // try catch (std::exception &e) @@ -2600,6 +2616,18 @@ int main(int argc, char *argv[]) } /* Program closing...*/ + endMainLoop(); + +#ifdef IOS_STK + // App store may not like this, but this can happen if player uses keyboard to quit stk + exit(0); + return 0; +#else + return 0 ; +#endif +} // main + +void endMainLoop() { #ifdef ENABLE_WIIUSE if(wiimote_manager) @@ -2651,15 +2679,7 @@ int main(int argc, char *argv[]) socketExit(); nifmExit(); #endif - -#ifdef IOS_STK - // App store may not like this, but this can happen if player uses keyboard to quit stk - exit(0); - return 0; -#else - return 0 ; -#endif -} // main +} // ============================================================================ #ifdef WIN32 diff --git a/src/main_loop.cpp b/src/main_loop.cpp index 8a06c869b16..5e3d8103fd0 100644 --- a/src/main_loop.cpp +++ b/src/main_loop.cpp @@ -84,6 +84,7 @@ extern "C" { #endif MainLoop* main_loop = 0; +double left_over_time = 0; #ifdef WIN32 LRESULT CALLBACK separateProcessProc(_In_ HWND hwnd, _In_ UINT uMsg, @@ -399,11 +400,12 @@ void MainLoop::updateRace(int ticks, bool fast_forward) */ void MainLoop::run() { +#ifndef __EMSCRIPTEN__ m_curr_time = std::chrono::steady_clock::now(); +#endif // DT keeps track of the leftover time, since the race update // happens in fixed timesteps - double left_over_time = 0; - + #ifdef WIN32 HANDLE parent = 0; if (m_parent_pid != 0) @@ -417,7 +419,10 @@ void MainLoop::run() } #endif +#ifndef __EMSCRIPTEN__ while (!m_abort) +#endif + { #ifdef __SWITCH__ // This feeds us messages (like when the Switch sleeps or requests an exit) @@ -708,6 +713,7 @@ void MainLoop::run() } } +#ifndef __EMSCRIPTEN__ if (!UserConfigParams::m_benchmark) { TimePoint frame_end = std::chrono::steady_clock::now(); @@ -734,6 +740,7 @@ void MainLoop::run() PROFILER_POP_CPU_MARKER(); } } +#endif PROFILER_POP_CPU_MARKER(); // MainLoop pop PROFILER_SYNC_FRAME(); diff --git a/src/states_screens/dialogs/custom_video_settings.cpp b/src/states_screens/dialogs/custom_video_settings.cpp index 9aad0d9ba92..395ecee2685 100644 --- a/src/states_screens/dialogs/custom_video_settings.cpp +++ b/src/states_screens/dialogs/custom_video_settings.cpp @@ -214,7 +214,7 @@ GUIEngine::EventPropagation CustomVideoSettingsDialog::processEvent(const std::s ModalDialog::dismiss(); OptionsScreenVideo::getInstance()->updateGfxSlider(); OptionsScreenVideo::getInstance()->updateBlurSlider(); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (update_needed && GE::getDriver()->getDriverType() == video::EDT_VULKAN) GE::getVKDriver()->updateDriver(true); #endif diff --git a/src/states_screens/kart_selection.cpp b/src/states_screens/kart_selection.cpp index 3ce44630e9e..7a92f79a1c4 100644 --- a/src/states_screens/kart_selection.cpp +++ b/src/states_screens/kart_selection.cpp @@ -496,7 +496,7 @@ void KartSelectionScreen::tearDown() { kart_properties_manager->clearFavoriteKartStatus(); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) GE::getGEConfig()->m_enable_draw_call_cache = false; GE::GEVulkanDriver* gevk = GE::getVKDriver(); if (gevk) diff --git a/src/states_screens/options/options_screen_display.cpp b/src/states_screens/options/options_screen_display.cpp index 68f1bc81434..cca86f77ade 100644 --- a/src/states_screens/options/options_screen_display.cpp +++ b/src/states_screens/options/options_screen_display.cpp @@ -390,6 +390,7 @@ void OptionsScreenDisplay::eventCallback(Widget* widget, const std::string& name rememberWinpos->setActive(!fullscreen->getState()); #ifndef SERVER_ONLY + #ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanDriver* gevk = GE::getVKDriver(); if (gevk && GE::getGEConfig()->m_fullscreen_desktop) { @@ -398,6 +399,7 @@ void OptionsScreenDisplay::eventCallback(Widget* widget, const std::string& name OptionsScreenDisplay::m_fullscreen_checkbox_focus = true; } else + #endif updateResolutionsList(); #endif } // fullscreen diff --git a/src/states_screens/options/options_screen_video.cpp b/src/states_screens/options/options_screen_video.cpp index 98807468938..cb4d0bcb35f 100644 --- a/src/states_screens/options/options_screen_video.cpp +++ b/src/states_screens/options/options_screen_video.cpp @@ -155,38 +155,48 @@ int OptionsScreenVideo::getImageQuality() void OptionsScreenVideo::setImageQuality(int quality) { #ifndef SERVER_ONLY + #ifdef _IRR_COMPILE_WITH_VULKAN_ GE::GEVulkanTextureDescriptor* td = NULL; if (GE::getVKDriver()) td = GE::getVKDriver()->getMeshTextureDescriptor(); + #endif switch (quality) { case 0: UserConfigParams::m_anisotropic = 4; UserConfigParams::m_high_definition_textures = 0x02; UserConfigParams::m_hq_mipmap = false; + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (td) td->setSamplerUse(GE::GVS_3D_MESH_MIPMAP_2); + #endif break; case 1: UserConfigParams::m_anisotropic = 16; UserConfigParams::m_high_definition_textures = 0x02; UserConfigParams::m_hq_mipmap = false; + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (td) td->setSamplerUse(GE::GVS_3D_MESH_MIPMAP_2); + #endif break; case 2: UserConfigParams::m_anisotropic = 16; UserConfigParams::m_high_definition_textures = 0x03; UserConfigParams::m_hq_mipmap = false; + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (td) td->setSamplerUse(GE::GVS_3D_MESH_MIPMAP_4); + #endif break; case 3: UserConfigParams::m_anisotropic = 16; UserConfigParams::m_high_definition_textures = 0x03; UserConfigParams::m_hq_mipmap = true; + #ifdef _IRR_COMPILE_WITH_VULKAN_ if (td) td->setSamplerUse(GE::GVS_3D_MESH_MIPMAP_16); + #endif break; default: assert(false); @@ -651,7 +661,7 @@ void OptionsScreenVideo::eventCallback(Widget* widget, const std::string& name, assert(level < (int)m_scale_rtts_custom_presets.size()); UserConfigParams::m_scale_rtts_factor = m_scale_rtts_custom_presets[level].value; -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) GE::GEVulkanDriver* gevk = GE::getVKDriver(); if (gevk && GE::getGEConfig()->m_render_scale != UserConfigParams::m_scale_rtts_factor) { diff --git a/src/tracks/graph.cpp b/src/tracks/graph.cpp index 810fbdefd91..b120e154b54 100644 --- a/src/tracks/graph.cpp +++ b/src/tracks/graph.cpp @@ -103,13 +103,13 @@ void Graph::createDebugMesh() } #endif -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) bool vk = (GE::getVKDriver() != NULL); if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = true; #endif m_node = irr_driver->addMesh(m_mesh, "track-debug-mesh"); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = false; #endif @@ -485,13 +485,13 @@ RenderTarget* Graph::makeMiniMap(const core::dimension2du &dimension, } #endif -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) bool vk = (GE::getVKDriver() != NULL); if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = true; #endif m_node = irr_driver->addMesh(m_mesh, "mini_map"); -#ifndef SERVER_ONLY +#if !defined(SERVER_ONLY) && defined(_IRR_COMPILE_WITH_VULKAN_) if (vk) GE::getGEConfig()->m_convert_irrlicht_mesh = false; #endif diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 00000000000..16004d03c15 --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,49 @@ +# SuperTuxKart WASM Port + +Currently this port is still incomplete. + +Working features: +- The game launches +- OpenGL ES 2 graphics (GLES 3 doesn't work) +- Audio +- Persistent user data +- Caching data and asset files in IndexedDB + +TODO: +- Fix GLES 3 (maybe someone with more experience in webgl could help here) +- Lazy load assets during gameplay +- Networking + +Caveats: +- The performance isn't great, probably because the legacy renderer is still being used +- Some options, like anything related to online multiplayer, may hang the game + +## Building +1. First, get a copy of the emsdk (it might help to have emscripten already installed with `sudo apt install emscripten`): +``` +wasm/get_emsdk.sh +``` +2. Compile all the dependencies: +``` +wasm/build_deps.sh +``` +3. Compile STK: +``` +wasm/build.sh +``` +4. Compress and bundle the game data and assets: +``` +sudo apt install imagemagick vorbis-tools pngquant advancecomp libjpeg-progs optipng +wasm/pack_assets.sh ../stk-assets +``` +5. Host a web server: +``` +(cd wasm/web && python3 ../run_server.py) +``` + +## Project Structure: +- /wasm/build - Files for building the dependencies +- /wasm/prefix - Headers and library files +- /wasm/web - Web server root directory +- /wasm/emsdk - Emscripten SDK +- /wasm/fragments - Patches for emscripten's generated JS \ No newline at end of file diff --git a/wasm/build.sh b/wasm/build.sh new file mode 100755 index 00000000000..92858ed0a4c --- /dev/null +++ b/wasm/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e +set -x + +BUILD_TYPE="${1:-'Release'}" + +CORE_COUNT="$(nproc --all)" +BASE_DIR="$(realpath "$(dirname "$0")")" +SRC_DIR="$(dirname "$BASE_DIR")" +WEB_DIR="$BASE_DIR/web" +BUILD_DIR="$SRC_DIR/cmake_build/$BUILD_TYPE" +EMSDK_DIR="$BASE_DIR/emsdk" + +mkdir -p $BUILD_DIR +cd $BUILD_DIR + +source "$EMSDK_DIR/emsdk_env.sh" +embuilder build sdl2 sdl2_ttf sdl2_image sdl2_image_jpg sdl2_image_png +emcmake cmake "$SRC_DIR" -DNO_SHADERC=on -DCMAKE_BUILD_TYPE=$BUILD_TYPE +make -j$CORE_COUNT + +echo "copying wasm files" +mkdir -p "$WEB_DIR/game" +cp ./bin/* "$WEB_DIR/game/" + +echo "applying patches" +python3 "$BASE_DIR/patch_js.py" "$BASE_DIR/fragments" "$WEB_DIR/game/supertuxkart.js" diff --git a/wasm/build_deps.sh b/wasm/build_deps.sh new file mode 100755 index 00000000000..89468efe268 --- /dev/null +++ b/wasm/build_deps.sh @@ -0,0 +1,182 @@ +#!/bin/bash + +set -x +set -e + +CORE_COUNT="$(nproc --all)" +BASE_DIR="$(realpath "$(dirname "$0")")" +BUILD_DIR="$BASE_DIR/build" +EMSDK_DIR="$BASE_DIR/emsdk" +PREFIX="$BASE_DIR/prefix" +INCLUDE="$PREFIX/include" +LIB="$PREFIX/lib" + +source $EMSDK_DIR/emsdk_env.sh +export PKG_CONFIG_PATH="$LIB/pkgconfig" +export CFLAGS="-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -pthread" +export CPPFLAGS="-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -pthread" +export LDFLAGS="-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -pthread" + +clone_repo() { + local url="$1" + local tag="$2" + local path="$3" + if [ ! -d "$path" ]; then + git clone "$url" -b "$tag" --depth=1 "$path" + fi +} + +build_ogg() { + local SRC_DIR="$BUILD_DIR/ogg" + clone_repo "https://github.com/xiph/ogg" v1.3.5 "$SRC_DIR" + cd "$SRC_DIR" + + ./autogen.sh + emconfigure ./configure --host=none-linux --prefix="$PREFIX" --disable-shared + emmake make -j$CORE_COUNT + make install +} + +build_vorbis() { + local SRC_DIR="$BUILD_DIR/vorbis" + clone_repo "https://github.com/xiph/vorbis" v1.3.7 "$SRC_DIR" + cd "$SRC_DIR" + + ./autogen.sh + emconfigure ./configure --host=none-linux --prefix="$PREFIX" --with-ogg="$PREFIX" --disable-shared + emmake make -j$CORE_COUNT + make install +} + +build_openssl() { + local SRC_DIR="$BUILD_DIR/openssl" + clone_repo "https://github.com/openssl/openssl" openssl-3.3.0 "$SRC_DIR" + cd "$SRC_DIR" + + emconfigure ./Configure linux-x32 -no-asm -static -no-afalgeng -no-dso -DOPENSSL_SYS_NETWARE -DSIG_DFL=0 -DSIG_IGN=0 -DHAVE_FORK=0 -DOPENSSL_NO_AFALGENG=1 -DOPENSSL_NO_SPEED=1 -DOPENSSL_NO_DYNAMIC_ENGINE -DDLOPEN_FLAG=0 + sed -i 's|^CROSS_COMPILE.*$|CROSS_COMPILE=|g' Makefile + emmake make -j$CORE_COUNT build_generated libssl.a libcrypto.a + cp -r include/openssl $PREFIX/include + cp libcrypto.a libssl.a $PREFIX/lib +} + +build_zlib() { + local SRC_DIR="$BUILD_DIR/zlib" + clone_repo "https://github.com/madler/zlib" v1.3.1 "$SRC_DIR" + cd "$SRC_DIR" + + emconfigure ./configure --prefix="$PREFIX" --static + emmake make -j$CORE_COUNT + make install +} + +build_curl() { + local SRC_DIR="$BUILD_DIR/curl" + clone_repo "https://github.com/curl/curl" curl-8_8_0 "$SRC_DIR" + cd "$SRC_DIR" + + autoreconf -fi + emconfigure ./configure --host none-linux --prefix="$PREFIX" \ + --with-ssl="$PREFIX" --with-zlib="$PREFIX" \ + --disable-shared --disable-threaded-resolver \ + --without-libpsl --disable-netrc --disable-ipv6 \ + --disable-tftp --disable-ntlm-wb + emmake make -j$CORE_COUNT + make install +} + +build_jpeg() { + local SRC_DIR="$BUILD_DIR/jpeg" + clone_repo "https://github.com/libjpeg-turbo/libjpeg-turbo" 3.0.3 "$SRC_DIR" + cd "$SRC_DIR" + mkdir -p build + cd build + + emcmake cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX" -DWITH_SIMD=0 -DENABLE_SHARED=0 .. + emmake make -j$CORE_COUNT + make install +} + +build_png() { + local SRC_DIR="$BUILD_DIR/png" + clone_repo "https://github.com/pnggroup/libpng" v1.6.43 "$SRC_DIR" + cd "$SRC_DIR" + + emconfigure ./configure --host none-linux --prefix="$PREFIX" --disable-shared CPPFLAGS="$CPPFLAGS -I$INCLUDE" LDFLAGS="$LDFLAGS -L$LIB" + emmake make -j$CORE_COUNT + make install +} + +build_freetype() { + local with_harfbuzz="$1" + local SRC_DIR="$BUILD_DIR/freetype" + clone_repo "https://github.com/freetype/freetype" VER-2-13-2 "$SRC_DIR" + cd "$SRC_DIR" + mkdir -p build + cd build + + if [ ! "$with_harfbuzz" ]; then + emcmake cmake .. -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX" \ + -DZLIB_LIBRARY="$LIB/libz.a" -DZLIB_INCLUDE_DIR="$INCLUDE" \ + -DPNG_LIBRARY="$LIB/libpng.a" -DPNG_PNG_INCLUDE_DIR="$INCLUDE" + else + emcmake cmake .. -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX" \ + -DZLIB_LIBRARY="$LIB/libz.a" -DZLIB_INCLUDE_DIR="$INCLUDE" \ + -DPNG_LIBRARY="$LIB/libpng.a" -DPNG_PNG_INCLUDE_DIR="$INCLUDE" \ + -DHarfBuzz_LIBRARY="$LIB/libharfbuzz.a" -DHarfBuzz_INCLUDE_DIR="$INCLUDE/harfbuzz/" \ + -DFT_REQUIRE_HARFBUZZ=TRUE + fi + emmake make -j$CORE_COUNT + make install +} + +build_harfbuzz() { + local SRC_DIR="$BUILD_DIR/harfbuzz" + clone_repo "https://github.com/harfbuzz/harfbuzz" 8.5.0 "$SRC_DIR" + cd "$SRC_DIR" + + NOCONFIGURE=1 ./autogen.sh + emconfigure ./configure --host=none-linux --prefix="$PREFIX" --disable-shared PKG_CONFIG_PATH="$LIB/pkgconfig" + emmake make -j$CORE_COUNT + make install +} + +if [ ! -d "$INCLUDE/ogg" ]; then + build_ogg +fi + +if [ ! -d "$INCLUDE/vorbis" ]; then + build_vorbis +fi + +if [ ! -d "$INCLUDE/openssl" ]; then + build_openssl +fi + +if [ ! -f "$INCLUDE/zlib.h" ]; then + build_zlib +fi + +if [ ! -d "$INCLUDE/curl" ]; then + build_curl +fi + +if [ ! -f "$INCLUDE/turbojpeg.h" ]; then + build_jpeg +fi + +if [ ! -f "$INCLUDE/png.h" ]; then + build_png +fi + +if [ ! -d "$INCLUDE/freetype2" ]; then + build_freetype +fi + +if [ ! -d "$INCLUDE/harfbuzz" ]; then + build_harfbuzz + + #rebuild freetype with harfbuzz support + rm -rf "$INCLUDE/freetype2" + build_freetype true +fi diff --git a/wasm/fragments/fix_webgl.js b/wasm/fragments/fix_webgl.js new file mode 100644 index 00000000000..a46ed7dc7ee --- /dev/null +++ b/wasm/fragments/fix_webgl.js @@ -0,0 +1,18 @@ +/* INSERT +GLctx.getAttachedShaders\(\S+?\) +*/ + || [] + +/* INSERT_BEFORE +GL.preDrawHandleClientVertexAttribBindings\([a-z]+?\) +*/ +try { + +/* INSERT +GL.preDrawHandleClientVertexAttribBindings\([a-z]+?\) +*/ + } catch (e) { //note: i don't know if this patch has any side effects. it seems to work for now even though i don't really understand why + if (!GLctx.currentElementArrayBufferBinding) { + GLctx.bindBuffer(0x8893 /*GL_ELEMENT_ARRAY_BUFFER*/, null); + } +} \ No newline at end of file diff --git a/wasm/get_emsdk.sh b/wasm/get_emsdk.sh new file mode 100755 index 00000000000..8a81f8ab994 --- /dev/null +++ b/wasm/get_emsdk.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +CORE_COUNT="$(nproc --all)" +BASE_DIR="$(realpath "$(dirname "$0")")" +EMSDK_DIR="$BASE_DIR/emsdk" + +git clone https://github.com/emscripten-core/emsdk.git --depth=1 "$EMSDK_DIR" +cd $EMSDK_DIR + +./emsdk update +./emsdk install latest +./emsdk activate latest \ No newline at end of file diff --git a/wasm/pack_assets.sh b/wasm/pack_assets.sh new file mode 100755 index 00000000000..9c42927f445 --- /dev/null +++ b/wasm/pack_assets.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e +set -x + +BASE_DIR="$(realpath "$(dirname "$0")")" +SRC_DIR="$(dirname "$BASE_DIR")" +WEB_DIR="$BASE_DIR/web" +ASSETS_DIR="$(realpath "$1")" +ASSETS_SCRIPT="$SRC_DIR/android/generate_assets.sh" + +LOW_QUALITY_DIR="$WEB_DIR/game/data_low" +MEDIUM_QUALITY_DIR="$WEB_DIR/game/data_mid" +HIGH_QUALITY_DIR="$WEB_DIR/game/data_high" + +create_manifest() { + local path="$1" + local file_name="$(basename "$path")" + local data_dir="$(dirname "$path")" + local size="$(du -b "$path" | cut -f1)" + local chunks="$(find "$data_dir" -name "$file_name.*" | sort)" + + echo "$size" + for chunk in $chunks; do + local chunk_name="$(basename "$chunk")" + local ending="$(echo "$chunk_name" | rev | cut -d'.' -f1 | rev)" + if [ "$ending" = "manifest" ]; then + continue + fi + echo "$chunk_name" + done +} + +pack_dir() { + local source_dir="$1" + local out_path="$2" + tar -cf - -C "$source_dir" . | gzip -9 - > "$out_path" + split -b 20m --numeric-suffixes "$out_path" "$out_path." + create_manifest "$out_path" > "$out_path.manifest" + rm "$out_path" +} + +generate_dir() { + local data_dir="$1" + local output_path="$2" + local texture_size="$3" + ASSETS_PATHS="$ASSETS_DIR" OUTPUT_PATH="$data_dir" TEXTURE_SIZE="$texture_size" $ASSETS_SCRIPT + (cd $data_dir/data && ./optimize_data.sh) + pack_dir "$data_dir/data" "$output_path" +} + +if [ ! "$ASSETS_DIR" ]; then + echo "assets not found" + exit 1 +fi + +generate_dir "$LOW_QUALITY_DIR" "$WEB_DIR/game/data_low.tar.gz" 256 +generate_dir "$MEDIUM_QUALITY_DIR" "$WEB_DIR/game/data_mid.tar.gz" 512 +generate_dir "$HIGH_QUALITY_DIR" "$WEB_DIR/game/data_high.tar.gz" 1024 \ No newline at end of file diff --git a/wasm/patch_js.py b/wasm/patch_js.py new file mode 100644 index 00000000000..f06606f9750 --- /dev/null +++ b/wasm/patch_js.py @@ -0,0 +1,31 @@ +import re +import sys +import pathlib + +match_regex = r'/\* (.+?)\n(.+?)\n\*/\n(.*?)(\n\n|$)' + +fragments_dir = sys.argv[1] +target_dir = sys.argv[2] +fragments_path = pathlib.Path(fragments_dir) +target_path = pathlib.Path(target_dir) +target_text = target_path.read_text() + +for fragment_file in fragments_path.iterdir(): + print(f"applying patch from {fragment_file.name}") + fragment_text = fragment_file.read_text() + matches = re.findall(match_regex, fragment_text, re.S) + + for mode, patch_regex, patch_text, _ in matches: + fragment_matches = re.findall(patch_regex, target_text) + if not fragment_matches: + print(f"warning: regex did not match anything for '{patch_regex}'"); + if mode == "DELETE": + target_text = re.sub(patch_regex, "", target_text) + elif mode == "REPLACE": + target_text = re.sub(patch_regex, patch_text, target_text) + elif mode == "INSERT": + target_text = re.sub("("+patch_regex+")", r'\1'+patch_text, target_text) + elif mode == "INSERT_BEFORE": + target_text = re.sub("("+patch_regex+")", patch_text+r'\1', target_text) + +target_path.write_text(target_text) \ No newline at end of file diff --git a/wasm/run_server.py b/wasm/run_server.py new file mode 100644 index 00000000000..f0edc713891 --- /dev/null +++ b/wasm/run_server.py @@ -0,0 +1,16 @@ +from http import server +import sys + +port = 8000 +if len(sys.argv) >= 2: + port = int(sys.argv[1]) + +class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + + server.SimpleHTTPRequestHandler.end_headers(self) + +if __name__ == '__main__': + server.test(HandlerClass=MyHTTPRequestHandler, port=port) diff --git a/wasm/web/404.html b/wasm/web/404.html new file mode 100644 index 00000000000..950973a5c5d --- /dev/null +++ b/wasm/web/404.html @@ -0,0 +1,10 @@ + + + + 404 + + +

404

+

Not found.

+ + \ No newline at end of file diff --git a/wasm/web/_headers b/wasm/web/_headers new file mode 100644 index 00000000000..6e0d001fdaf --- /dev/null +++ b/wasm/web/_headers @@ -0,0 +1,3 @@ +/* + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp \ No newline at end of file diff --git a/wasm/web/favicon.ico b/wasm/web/favicon.ico new file mode 100644 index 00000000000..43d0440228f Binary files /dev/null and b/wasm/web/favicon.ico differ diff --git a/wasm/web/index.html b/wasm/web/index.html new file mode 100644 index 00000000000..c9bb7fe5541 --- /dev/null +++ b/wasm/web/index.html @@ -0,0 +1,104 @@ + + + + + + SuperTuxKart + + + + + + + + + + +
+
+ +

SuperTuxKart Wasm

+
+

This is an experimental port of SuperTuxKart to the browser using WebAssembly and Emscripten. Currently, everything works except for some rendering APIs and networking.

+

There is a 120MB initial download required to play, and the game will require around 500MB of memory.

+

This web port was made by ading2210, and the source code is located at github.com/ading2210/stk-code.

+

If you want to check on the status of this port, take a look at the corresponding pull request in the SuperTuxKart repository.

+ + +
+ +

Loading game code...

+
+
+ + \ No newline at end of file diff --git a/wasm/web/script.js b/wasm/web/script.js new file mode 100644 index 00000000000..959fea35083 --- /dev/null +++ b/wasm/web/script.js @@ -0,0 +1,237 @@ +import pako from "https://cdn.jsdelivr.net/npm/pako@2.1.0/+esm"; +import jsUntar from "https://cdn.jsdelivr.net/npm/js-untar@2.0.0/+esm"; + +let db = null; +let db_name = "stk_db"; +let store_name = "stk_store"; +let idbfs_mount = null; +let data_version = 1; + +let start_button = document.getElementById("start_button"); +let status_text = document.getElementById("status_text"); +let info_container = document.getElementById("info_container"); +let quality_select = document.getElementById("quality_select"); + +function load_db() { + if (db) return db; + return new Promise((resolve, reject) => { + let request = indexedDB.open(db_name, 1); + request.onerror = (event) => { + reject(event); + }; + request.onsuccess = (event) => { + db = event.target.result; + resolve(db); + }; + request.onupgradeneeded = (event) => { + let db = event.target.result; + db.createObjectStore(store_name); + }; + }); +} + +function request_async(request) { + return new Promise((resolve, reject) => { + request.onerror = (event) => { + reject(event); + }; + request.onsuccess = () => { + resolve(request.result); + }; + }); +} + +async function delete_db() { + if (db) { + db.close(); + db = null; + } + await request_async(indexedDB.deleteDatabase(db_name)); +} + +async function load_store() { + await load_db(); + let transaction = db.transaction(store_name, "readwrite"); + let store = transaction.objectStore(store_name); + return store; +} + +async function check_db(key) { + let store = await load_store(); + return await request_async(store.count(key)); +} + +async function read_db(key) { + let store = await load_store(); + return await request_async(store.get(key)); +} + +async function write_db(key, data) { + let store = await load_store(); + return await request_async(store.put(data, key)); +} + +async function read_db_chunks(key) { + let {size, chunk_count} = await read_db(key); + let offset = 0; + let array = new Uint8Array(size); + + for (let i = 0; i < chunk_count; i++) { + let chunk_array = await read_db(key + "." + i); + array.set(chunk_array, offset); + offset += chunk_array.length; + } + + return array; +} + +async function write_db_chunks(key, data) { + let size = data.length; + let chunk_size = 20_000_000; + let chunk_count = Math.ceil(size / chunk_size); + + let offset = 0; + for (let i = 0; i < chunk_count; i++) { + let chunk_array = data.slice(offset, offset + chunk_size); + await write_db(key + "." + i, chunk_array); + offset += chunk_size; + } + + await write_db(key, {size, chunk_count}); +} + +async function download_chunks(url) { + let path = url.split("/"); + let filename = path.pop(); + let base_url = path.join("/"); + + status_text.textContent = `Downloading manifest for ${filename}...`; + let r1 = await fetch(url + ".manifest"); + let manifest = (await r1.text()).split("\n"); + let size = parseInt(manifest.shift()); + manifest.pop(); + + let offset = 0; + let chunk = null; + let array = new Uint8Array(size); + let chunk_count = manifest.length; + let current_chunk = 1; + + while (chunk = manifest.shift()) { + let mb_progress = Math.floor(offset / (1024 ** 2)) + let mb_total = Math.floor(size / (1024 ** 2)) + status_text.textContent = `Downloading ${filename}... (chunk ${current_chunk}/${chunk_count}, ${mb_progress}/${mb_total}MiB)`; + + let r2 = await fetch(base_url + "/" + chunk); + let buffer = await r2.arrayBuffer(); + let chunk_array = new Uint8Array(buffer); + array.set(chunk_array, offset); + offset += chunk_array.length; + current_chunk++; + } + + return array.buffer; +} + +async function extract_tar(url, fs_path, use_cache = false) { + //download tar file from server and decompress + let decompressed; + if (!use_cache || !await check_db(url)) { + let filename = url.split("/").pop(); + let compressed = await download_chunks(url); + status_text.textContent = `Decompressing ${filename}...`; + decompressed = pako.inflate(compressed); + compressed = null; + if (use_cache) { + status_text.textContent = `Saving ${filename} to the cache...`; + await write_db_chunks(url, decompressed); + } + } + else { + decompressed = await read_db_chunks(url); + } + + //read the tar file and add to emscripten's fs + let files = await jsUntar(decompressed.buffer); + for (let file of files) { + let relative_path = file.name.substring(1); + let out_path = fs_path + relative_path; + if (out_path.endsWith("/")) { + try { + FS.mkdir(out_path); + } + catch {} + } + else { + let array = new Uint8Array(file.buffer); + FS.writeFile(out_path, array); + file.buffer = null; + } + } +} + +async function load_data() { + //check if we need to update the assets bundle + if (!await check_db("/version") || !(await read_db("/version") == data_version)) { + await delete_db(); + await write_db("/version", data_version); + } + + let quality = quality_select.value; + let data_url = `/game/data_${quality}.tar.gz`; + await extract_tar(data_url, "/data", true); +} + +async function load_idbfs() { + idbfs_mount = FS.mount(IDBFS, {}, "/home/web_user").mount; + await sync_idbfs(true); +} + +function sync_idbfs(populate = false) { + return new Promise((resolve, reject) => { + idbfs_mount.type.syncfs(idbfs_mount, populate, (err) => { + if (err) reject(err); + else resolve(); + }); + }) +} + +function wait_for_frame() { + return new Promise((resolve) => {requestAnimationFrame(resolve)}); +} + +async function main() { + globalThis.ready = true; + await load_idbfs(); + start_button.onclick = start_game; + start_button.disabled = false; + status_text.innerText = ""; +} + +async function start_game() { + status_text.textContent = "Loading game files..."; + start_button.disabled = true; + await load_data(); + await wait_for_frame(); + status_text.textContent = "Launching game..."; + + await wait_for_frame(); + run(); + info_container.style.zIndex = 0; + info_container.style.display = "none"; + sync_idbfs(); + + console.warn("Warning: Opening devtools may harm the game's performance."); +} + +Module["canvas"] = document.getElementById("canvas") +globalThis.main = main; +globalThis.sync_idbfs = sync_idbfs; +globalThis.load_idbfs = load_idbfs; + +let poll_runtime_interval = setInterval(() => { + if (globalThis.ready) { + main(); + clearInterval(poll_runtime_interval); + } +}, 100); \ No newline at end of file diff --git a/wasm/web/supertuxkart_64.png b/wasm/web/supertuxkart_64.png new file mode 100644 index 00000000000..6beb8e609b8 Binary files /dev/null and b/wasm/web/supertuxkart_64.png differ