diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 0000000000..5a8f09e0ac --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,61 @@ +name: iOS + +# build on c/cpp changes or workflow changes +on: + push: + paths: + - 'lib/**.[ch]' + - 'lib/**.cpp' + - 'src/**.[ch]' + - 'src/**.cpp' + - 'irr/**.[ch]' + - 'irr/**.cpp' + - '**/CMakeLists.txt' + - 'cmake/Modules/**' + - 'po/**.po' + - '.github/workflows/ios.yml' + pull_request: + paths: + - 'lib/**.[ch]' + - 'lib/**.cpp' + - 'src/**.[ch]' + - 'src/**.cpp' + - 'irr/**.[ch]' + - 'irr/**.cpp' + - '**/CMakeLists.txt' + - 'cmake/Modules/**' + - 'po/**.po' + - '.github/workflows/ios.yml' + +jobs: + build-ios: + strategy: + matrix: + osver: [18.2] + xcodever: [16.2] + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + + - name: Prepare environment + run: | + echo "REPDIR=$(pwd)" >> $GITHUB_ENV + echo "osver=${{matrix.osver}}" >> $GITHUB_ENV + echo "xcodever=${{matrix.xcodever}}" >> $GITHUB_ENV + + - name: Install deps + run: | + source ./util/ci/common.sh + install_ios_deps $osver + + - name: Build and Archive with Xcode + run: | + mkdir build + cd build + export DEPS_DIR=${REPDIR}/ios${osver}_deps/iPhoneSimulator + ../util/ci/build_ios.sh + + - name: Run in iPhoneSimulator + run: | + ./util/ci/run_ios.sh "iPad Air 13-inch (M2)" 150 + cat log.txt diff --git a/cmake/Modules/FindZstd.cmake b/cmake/Modules/FindZstd.cmake index e28e1334b9..e527e1b6f0 100644 --- a/cmake/Modules/FindZstd.cmake +++ b/cmake/Modules/FindZstd.cmake @@ -1,5 +1,8 @@ mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR) +message(STATUS "ZSTD_LIBRARY: ${ZSTD_LIBRARY}") +message(STATUS "ZSTD_INCLUDE_DIR: ${ZSTD_INCLUDE_DIR}") + find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) find_library(ZSTD_LIBRARY NAMES zstd) diff --git a/doc/compiling/README.md b/doc/compiling/README.md index 9ce8a800e9..ecaa5f431d 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -24,6 +24,7 @@ General options and their default values: PRECOMPILED_HEADERS_PATH= - Path to a file listing all headers to precompile (default points to src/precompiled_headers.txt) USE_SDL2=TRUE - Build with SDL2; Enables IrrlichtMt device SDL2 USE_SDL2_STATIC=TRUE - Links with SDL2::SDL2-static instead of SDL2::SDL2 + USE_ANGLE=TRUE - Build with Google ANGLE; IrrlichMt device SDL2 on iOS ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal) ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations diff --git a/doc/compiling/ios.md b/doc/compiling/ios.md new file mode 100644 index 0000000000..629d533831 --- /dev/null +++ b/doc/compiling/ios.md @@ -0,0 +1,34 @@ +# Compiling for iOS + +THIS DOCUMENT IS NOT FINISHED!!! + +## Requirements + +- macOS +- [Homebrew](https://brew.sh/) +- XCode +- iOS/IPhoneSimulator SDK +- iOS Simulator for newest iOS is supported only on Apple Silicon deviecs. + +Install dependencies with homebrew: + +``` +brew install cmake git +``` + +## Generate Xcode project + +This script will download and build all requested dependencies for iOS or IPhoneSimulator and generate Xcode project for Luanti. + +```bash +/path/to/ios_build_with_deps.sh https://github.com/luanti-org/luanti.git master ../luanti_ios ../luanti_ios_deps Debug "iPhoneSimulator" "18.2" "all" +``` + +### Build and Run + +* Open generated Xcode project +* Select scheme `luanti` +* Configure signing Team etc. +* Run Build command +* Open application from `build/build/Debug/` directory or run it from Xcode + diff --git a/irr/README.md b/irr/README.md index eb7f14809f..e68856d0b2 100644 --- a/irr/README.md +++ b/irr/README.md @@ -32,6 +32,7 @@ We aim to support these platforms: * Windows via MinGW * Linux (GL or GLES) * macOS +* iOS (GLES) * Android This doesn't mean other platforms don't work or won't be supported, if you find something that doesn't work contributions are welcome. @@ -51,7 +52,7 @@ Driver (rows) vs Device (columns) Notes: -* [1] `CIrrDeviceSDL`: supports Android, Linux, macOS, Windows +* [1] `CIrrDeviceSDL`: supports Android, Linux, macOS, iOS, Windows * [2] `CIrrDeviceLinux`: supports Linux * [3] `CIrrDeviceOSX`: supports macOS * [4] `CIrrDeviceWin32`: supports Windows diff --git a/irr/src/CFileSystem.cpp b/irr/src/CFileSystem.cpp index 16d8e372f1..03105c5b37 100644 --- a/irr/src/CFileSystem.cpp +++ b/irr/src/CFileSystem.cpp @@ -22,7 +22,7 @@ #include // for _chdir #include // for _access #include -#elif (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_) || defined(_IRR_ANDROID_PLATFORM_)) +#elif (defined(_IRR_POSIX_API_) || defined(_IRR_OSX_PLATFORM_) || defined(_IRR_IOS_PLATFORM_) || defined(_IRR_ANDROID_PLATFORM_) ) #include #include #include diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index a44213d7cc..a39fadac73 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2002-2012 Nikolaus Gebhardt +// Copyright (C) 2025 Luanti contributors // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h @@ -29,6 +30,13 @@ #include "CSDLManager.h" +#ifdef _IRR_COMPILE_WITH_ANGLE_ +#include +#include +#include +#include +#endif + // Since SDL doesn't have mouse keys as keycodes we need to fall back to EKEY_CODE in some cases. static inline bool is_fake_key(EKEY_CODE key) { switch (key) { @@ -409,13 +417,32 @@ CIrrDeviceSDL::~CIrrDeviceSDL() for (u32 i = 0; i < numJoysticks; ++i) SDL_JoystickClose(Joysticks[i]); #endif +#ifndef _IRR_COMPILE_WITH_ANGLE_ if (Window && Context) { SDL_GL_MakeCurrent(Window, NULL); SDL_GL_DeleteContext(Context); } +#else + if (Display != EGL_NO_DISPLAY) { + eglMakeCurrent(Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + + if (Surface != EGL_NO_SURFACE) { + eglDestroySurface(Display, Surface); + } + if (Context != EGL_NO_CONTEXT) { + eglDestroyContext(Display, Context); + } + if (Display != EGL_NO_DISPLAY) { + eglTerminate(Display); + } + if (View) { + SDL_Metal_DestroyView(View); + } if (Window) { SDL_DestroyWindow(Window); } +#endif if (--SDLDeviceInstances == 0) { SDL_Quit(); @@ -529,21 +556,9 @@ bool CIrrDeviceSDL::createWindow() return false; } -bool CIrrDeviceSDL::createWindowWithContext() -{ - u32 SDL_Flags = 0; - SDL_Flags |= SDL_WINDOW_ALLOW_HIGHDPI; - - SDL_Flags |= getFullscreenFlag(CreationParams.Fullscreen); - if (Resizable) - SDL_Flags |= SDL_WINDOW_RESIZABLE; - if (CreationParams.WindowMaximized) - SDL_Flags |= SDL_WINDOW_MAXIMIZED; - SDL_Flags |= SDL_WINDOW_OPENGL; - - SDL_GL_ResetAttributes(); - #ifdef _IRR_EMSCRIPTEN_PLATFORM_ +bool CIrrDeviceSDL::createWindowWithContextEmscripten() +{ if (Width != 0 || Height != 0) emscripten_set_canvas_size(Width, Height); else { @@ -581,7 +596,11 @@ bool CIrrDeviceSDL::createWindowWithContext() emscripten_set_mouseleave_callback("#canvas", (void *)this, false, MouseLeaveCallback); return true; -#else // !_IRR_EMSCRIPTEN_PLATFORM_ +} +#else // _IRR_EMSCRIPTEN_PLATFORM_ +#ifndef _IRR_COMPILE_WITH_ANGLE_ +bool CIrrDeviceSDL::createWindowWithContextSDL() +{ switch (CreationParams.DriverType) { case video::EDT_OPENGL: SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); @@ -629,7 +648,12 @@ bool CIrrDeviceSDL::createWindowWithContext() SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); } - Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags); + if (!SDL_GL_LoadLibrary(NULL)) { + os::Printer::log("Could not load OpenGL ES library", SDL_GetError(), ELL_WARNING); + return false; + } + + Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI); if (!Window) { os::Printer::log("Could not create window", SDL_GetError(), ELL_WARNING); return false; @@ -643,6 +667,121 @@ bool CIrrDeviceSDL::createWindowWithContext() return false; } + int major, minor; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); + os::Printer::log("OpenGL ES version", std::to_string(major) + "." + std::to_string(minor), ELL_INFORMATION); + + const char* error = SDL_GetError(); + if (*error != '\0') { + os::Printer::log("SDL Error", error, ELL_WARNING); + SDL_ClearError(); + } +} +#else // _IRR_COMPILE_WITH_ANGLE_ +bool CIrrDeviceSDL::createWindowWithContextANGLE() +{ + Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_WINDOW_METAL | SDL_WINDOW_ALLOW_HIGHDPI); + if (!Window) { + os::Printer::log("Could not create window", SDL_GetError(), ELL_WARNING); + return false; + } + + auto metal_view = SDL_Metal_CreateView(Window); + auto metal_layer = SDL_Metal_GetLayer(metal_view); + + EGLAttrib egl_display_attribs[] = { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, + EGL_POWER_PREFERENCE_ANGLE, EGL_HIGH_POWER_ANGLE, + EGL_NONE + }; + + Display = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*) EGL_DEFAULT_DISPLAY, egl_display_attribs); + if (Display == EGL_NO_DISPLAY) + { + os::Printer::log("Failed to get EGL display"); + return false; + } + + if (eglInitialize(Display, NULL, NULL) == false) + { + os::Printer::log("Failed to initialize EGL"); + return false; + } + + EGLint egl_config_attribs[] = { + EGL_RED_SIZE, (CreationParams.Bits == 16 ? 5 : 8), + EGL_GREEN_SIZE, (CreationParams.Bits == 16 ? 5 : 8), + EGL_BLUE_SIZE, (CreationParams.Bits == 16 ? 5 : 8), + EGL_ALPHA_SIZE, (CreationParams.WithAlphaChannel ? (CreationParams.Bits == 16 ? 1 : 8) : 0), + EGL_DEPTH_SIZE, CreationParams.ZBufferBits, + EGL_STENCIL_SIZE, CreationParams.Stencilbuffer ? 8 : 0, + EGL_SAMPLE_BUFFERS, CreationParams.AntiAlias > 1 ? 1 : 0, + EGL_SAMPLES, CreationParams.AntiAlias > 1 ? CreationParams.AntiAlias : 0, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_NONE + }; + + EGLConfig config; + EGLint configs_count; + if (!eglChooseConfig(Display, egl_config_attribs, &config, 1, &configs_count)) + { + os::Printer::log("Failed to choose EGL config"); + return false; + } + + EGLint egl_context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + Context = eglCreateContext(Display, config, EGL_NO_CONTEXT, egl_context_attribs); + if (Context == EGL_NO_CONTEXT) { + os::Printer::log("Failed to create EGL context"); + return false; + } + + Surface = eglCreateWindowSurface(Display, config, metal_layer, NULL); + if (Surface == EGL_NO_SURFACE) + { + os::Printer::log("Failed to create EGL surface"); + return false; + } + + if (!eglMakeCurrent(Display, Surface, Surface, Context)) + { + os::Printer::log("Failed to make EGL context current"); + return false; + } +} +#endif // _IRR_COMPILE_WITH_ANGLE_ +#endif // _IRR_EMSCRIPTEN_PLATFORM_ + +bool CIrrDeviceSDL::createWindowWithContext() +{ + u32 SDL_Flags = 0; + SDL_Flags |= SDL_WINDOW_ALLOW_HIGHDPI; + + SDL_Flags |= getFullscreenFlag(CreationParams.Fullscreen); + if (Resizable) + SDL_Flags |= SDL_WINDOW_RESIZABLE; + if (CreationParams.WindowMaximized) + SDL_Flags |= SDL_WINDOW_MAXIMIZED; + SDL_Flags |= SDL_WINDOW_OPENGL; + //SDL_Flags |= SDL_WINDOW_METAL; + + SDL_GL_ResetAttributes(); + +#ifdef _IRR_EMSCRIPTEN_PLATFORM_ + createWindowWithContextEmscripten(); +#else // !_IRR_EMSCRIPTEN_PLATFORM_ + +#ifndef _IRR_COMPILE_WITH_ANGLE_ + createWindowWithContextSDL(); +#else + createWindowWithContextANGLE(); +#endif + updateSizeAndScale(); if (ScaleX != 1.0f || ScaleY != 1.0f) { // The given window size is in pixels, not in screen coordinates. @@ -1161,7 +1300,11 @@ float CIrrDeviceSDL::getDisplayDensity() const void CIrrDeviceSDL::SwapWindow() { +#ifndef _IRR_COMPILE_WITH_ANGLE_ SDL_GL_SwapWindow(Window); +#else + eglSwapBuffers(Display, Surface); +#endif } //! pause execution temporarily diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index 30c65431b6..517467139e 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -23,6 +23,10 @@ #undef SDL_VIDEO_DRIVER_DIRECTFB #include +#ifdef _IRR_COMPILE_WITH_ANGLE_ +#include +#endif + #include #include @@ -302,13 +306,30 @@ private: void createDriver(); bool createWindow(); +#ifdef _IRR_EMSCRIPTEN_PLATFORM_ + bool createWindowWithContextEmscripten(); +#else // _IRR_EMSCRIPTEN_PLATFORM_ +#ifndef _IRR_COMPILE_WITH_ANGLE_ + bool createWindowWithContextSDL(); +#else // _IRR_COMPILE_WITH_ANGLE_ + bool createWindowWithContextANGLE(); +#endif // _IRR_COMPILE_WITH_ANGLE_ +#endif // _IRR_EMSCRIPTEN_PLATFORM_ bool createWindowWithContext(); void createKeyMap(); void logAttributes(); - SDL_GLContext Context; + SDL_Window *Window; +#ifndef _IRR_COMPILE_WITH_ANGLE_ + SDL_GLContext Context; +#else + SDL_MetalView View; + EGLSurface Surface; + EGLContext Context; + EGLDisplay Display; +#endif #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) core::array Joysticks; #endif diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index f301b5d6a6..a2e8ed94e9 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -1,6 +1,11 @@ set(DEFAULT_SDL2 ON) +if(APPLE AND CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(DEFAULT_ANGLE ON) +endif() + option(USE_SDL2 "Use the SDL2 backend" ${DEFAULT_SDL2}) +option(USE_ANGLE "Use the Google ANGLE EGL and OpenGL ES" ${DEFAULT_ANGLE}) option(USE_SDL2_STATIC "Link with SDL2 static libraries" FALSE) @@ -64,8 +69,17 @@ if(WIN32) add_compile_definitions(_IRR_WINDOWS_ _IRR_WINDOWS_API_) set(DEVICE "WINDOWS") elseif(APPLE) - add_compile_definitions(_IRR_OSX_PLATFORM_) - set(DEVICE "OSX") + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_compile_definitions(-D_IRR_OSX_PLATFORM_) + set(DEVICE "OSX") + elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS") + add_compile_definitions(-D_IRR_IOS_PLATFORM_) + if(NOT USE_SDL2) + message(FATAL_ERROR "The iOS build requires SDL2") + endif() + else() + message(FATAL_ERROR "Not supported Apple device.") + endif() elseif(ANDROID) add_compile_definitions(_IRR_ANDROID_PLATFORM_) if(NOT USE_SDL2) @@ -100,6 +114,10 @@ endif() add_compile_definitions("_IRR_COMPILE_WITH_${DEVICE}_DEVICE_") +if(USE_ANGLE) + add_definitions("-D_IRR_COMPILE_WITH_ANGLE_") +endif() + # X11 if(DEVICE STREQUAL "X11") @@ -125,7 +143,7 @@ endif() # OpenGL if(USE_SDL2) - if(NOT ANDROID) + if(NOT ANDROID AND NOT USE_ANGLE) set(DEFAULT_OPENGL3 TRUE) endif() else() @@ -133,17 +151,17 @@ else() endif() option(ENABLE_OPENGL3 "Enable OpenGL 3+" ${DEFAULT_OPENGL3}) -if(ANDROID OR EMSCRIPTEN) +if(ANDROID OR EMSCRIPTEN OR USE_ANGLE) set(ENABLE_OPENGL FALSE) else() option(ENABLE_OPENGL "Enable OpenGL" TRUE) endif() -if(APPLE) +if(APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") set(ENABLE_GLES2 FALSE) set(ENABLE_WEBGL1 FALSE) else() - if(ANDROID OR EMSCRIPTEN) + if(ANDROID OR EMSCRIPTEN OR USE_ANGLE) set(DEFAULT_GLES2 TRUE) endif() if(EMSCRIPTEN) @@ -231,10 +249,10 @@ if(USE_SDL2) if(NOT USE_SDL2_STATIC) set(USE_SDL2_SHARED TRUE) endif() - if(NOT ANDROID) + if(NOT ANDROID AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") find_package(SDL2 REQUIRED) else() - # provided by AndroidLibs.cmake + # provided by AndroidLibs.cmake or from cmake call endif() message(STATUS "Found SDL2: ${SDL2_LIBRARIES}") @@ -280,7 +298,7 @@ endif() if(ANDROID) enable_language(C) -elseif(APPLE) +elseif(APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") find_library(COCOA_LIB Cocoa REQUIRED) find_library(IOKIT_LIB IOKit REQUIRED) @@ -600,6 +618,26 @@ target_link_libraries(IrrlichtMt PRIVATE "$<$:${X11_Xi_LIB}>" ) +if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + target_link_libraries(IrrlichtMt PRIVATE + "-framework UIKit" + "-framework Foundation" + "-framework CoreGraphics" + "-framework QuartzCore" + "-framework AudioToolbox" + "-framework AVFoundation" + "-framework CoreAudio" + "-framework CoreMotion" + "-framework IOSurface" + ${OPENGLES2_LIBRARY} + ${SDL2_LIBRARIES} + ) +else() + target_link_libraries(IrrlichtMt PRIVATE + "$<$:SDL2::SDL2>" + ) +endif() + if(WIN32) target_compile_definitions(IrrlichtMt INTERFACE _IRR_WINDOWS_API_) endif() diff --git a/irr/src/COSOperator.cpp b/irr/src/COSOperator.cpp index d2c10b6646..20659b2972 100644 --- a/irr/src/COSOperator.cpp +++ b/irr/src/COSOperator.cpp @@ -11,7 +11,7 @@ #include #ifndef _IRR_ANDROID_PLATFORM_ #include -#ifdef _IRR_OSX_PLATFORM_ +#if defined(_IRR_OSX_PLATFORM_) || defined(_IRR_IOS_PLATFORM_) #include #endif #endif diff --git a/irr/src/CSDLManager.cpp b/irr/src/CSDLManager.cpp index 96f193c833..29453b8aa9 100644 --- a/irr/src/CSDLManager.cpp +++ b/irr/src/CSDLManager.cpp @@ -33,7 +33,11 @@ bool CSDLManager::activateContext(const SExposedVideoData &videoData, bool resto void *CSDLManager::getProcAddress(const std::string &procName) { +#ifndef _IRR_COMPILE_WITH_ANGLE_ return SDL_GL_GetProcAddress(procName.c_str()); +#else + return (void *)eglGetProcAddress(procName.c_str()); +#endif } bool CSDLManager::swapBuffers() diff --git a/irr/src/OpenGL/Common.h b/irr/src/OpenGL/Common.h index fbde39d3b1..b26571c989 100644 --- a/irr/src/OpenGL/Common.h +++ b/irr/src/OpenGL/Common.h @@ -7,7 +7,7 @@ #include "irrTypes.h" // even though we have mt_opengl.h our driver code still uses GL_* constants -#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) && !defined(_IRR_COMPILE_WITH_ANGLE_) #include #include #else diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 025eeb269e..5cf5557c77 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -23,6 +23,8 @@ #include "mt_opengl.h" +#include + namespace video { @@ -214,14 +216,20 @@ void COpenGL3DriverBase::initQuadsIndices(u32 max_vertex_count) void COpenGL3DriverBase::initVersion() { + TEST_GL_ERROR(this); Name = GL.GetString(GL_VERSION); + TEST_GL_ERROR(this); printVersion(); // print renderer information + TEST_GL_ERROR(this); VendorName = GL.GetString(GL_RENDERER); + TEST_GL_ERROR(this); os::Printer::log("Renderer", VendorName.c_str(), ELL_INFORMATION); + TEST_GL_ERROR(this); Version = getVersionFromOpenGL(); + TEST_GL_ERROR(this); } bool COpenGL3DriverBase::isVersionAtLeast(int major, int minor) const noexcept @@ -235,9 +243,13 @@ bool COpenGL3DriverBase::isVersionAtLeast(int major, int minor) const noexcept bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenSize, bool stencilBuffer) { + TEST_GL_ERROR(this); initVersion(); + TEST_GL_ERROR(this); initFeatures(); + TEST_GL_ERROR(this); printTextureFormats(); + TEST_GL_ERROR(this); if (EnableErrorTest) { if (KHRDebugSupported) { @@ -259,6 +271,7 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS StencilBuffer = stencilBuffer; + TEST_GL_ERROR(this); GL.PixelStorei(GL_PACK_ALIGNMENT, 1); for (s32 i = 0; i < ETS_COUNT; ++i) @@ -266,15 +279,19 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS GL.ClearDepthf(1.0f); + TEST_GL_ERROR(this); GL.FrontFace(GL_CW); // create material renderers + TEST_GL_ERROR(this); createMaterialRenderers(); + TEST_GL_ERROR(this); // set the renderstates setRenderStates3DMode(); // set fog mode + TEST_GL_ERROR(this); setFog(FogColor, FogType, FogStart, FogEnd, FogDensity, PixelFog, RangeFog); // We need to reset once more at the beginning of the first rendering. @@ -725,6 +742,7 @@ void COpenGL3DriverBase::draw2DImage(const video::ITexture *texture, const core: const core::rect &sourceRect, const core::rect *clipRect, const video::SColor *const colors, bool useAlphaChannelOfTexture) { + TEST_GL_ERROR(this); if (!texture) return; @@ -896,6 +914,7 @@ void COpenGL3DriverBase::draw2DImageBatch(const video::ITexture *texture, if (clipRect) GL.Disable(GL_SCISSOR_TEST); + TEST_GL_ERROR(this); } //! draw a 2d rectangle @@ -976,13 +995,16 @@ void COpenGL3DriverBase::drawArrays(GLenum primitiveType, const VertexType &vert beginDraw(vertexType, reinterpret_cast(vertices)); GL.DrawArrays(primitiveType, 0, vertexCount); endDraw(vertexType); + TEST_GL_ERROR(this); } void COpenGL3DriverBase::drawElements(GLenum primitiveType, const VertexType &vertexType, const void *vertices, int vertexCount, const u16 *indices, int indexCount) { + TEST_GL_ERROR(this); beginDraw(vertexType, reinterpret_cast(vertices)); GL.DrawRangeElements(primitiveType, 0, vertexCount - 1, indexCount, GL_UNSIGNED_SHORT, indices); endDraw(vertexType); + TEST_GL_ERROR(this); } void COpenGL3DriverBase::drawGeneric(const void *vertices, const void *indexList, @@ -1189,6 +1211,7 @@ void COpenGL3DriverBase::setRenderStates3DMode() //! Can be called by an IMaterialRenderer to make its work easier. void COpenGL3DriverBase::setBasicRenderStates(const SMaterial &material, const SMaterial &lastmaterial, bool resetAllRenderStates) { + TEST_GL_ERROR(this); // ZBuffer switch (material.ZBuffer) { case ECFN_DISABLED: @@ -1341,6 +1364,7 @@ void COpenGL3DriverBase::setBasicRenderStates(const SMaterial &material, const S // Texture parameters setTextureRenderStates(material, resetAllRenderStates); + TEST_GL_ERROR(this); } //! Compare in SMaterial doesn't check texture parameters, so we should call this on each OnRender call. @@ -1491,6 +1515,7 @@ void COpenGL3DriverBase::setRenderStates2DMode(bool alpha, bool texture, bool al } MaterialRenderer2DActive->OnRender(this, video::EVT_STANDARD); + TEST_GL_ERROR(this); } void COpenGL3DriverBase::chooseMaterial2D() diff --git a/irr/src/OpenGL/ExtensionHandler.h b/irr/src/OpenGL/ExtensionHandler.h index ec75e492a1..6f6beef4e0 100644 --- a/irr/src/OpenGL/ExtensionHandler.h +++ b/irr/src/OpenGL/ExtensionHandler.h @@ -163,6 +163,8 @@ public: inline void irrGlObjectLabel(GLenum identifier, GLuint name, const char *label) { + // KHR_debug implements ObjectLabelKHR in OpenGL ES +#ifndef _IRR_COMPILE_WITH_OGLES2_ if (KHRDebugSupported) { u32 len = static_cast(strlen(label)); // Since our texture strings can get quite long we also truncate @@ -170,6 +172,7 @@ public: len = std::min(len, std::min(MaxLabelLength, 82U)); GL.ObjectLabel(identifier, name, len, label); } +#endif } bool LODBiasSupported = false; diff --git a/irr/src/OpenGLES2/DriverGLES2.cpp b/irr/src/OpenGLES2/DriverGLES2.cpp index 1ef6d091f8..39b8b49bcc 100644 --- a/irr/src/OpenGLES2/DriverGLES2.cpp +++ b/irr/src/OpenGLES2/DriverGLES2.cpp @@ -8,6 +8,8 @@ #include "mt_opengl.h" #include "CColorConverter.h" +#include + namespace video { diff --git a/lib/sha256/sha256.c b/lib/sha256/sha256.c index cd78e24f6a..1cb5029857 100644 --- a/lib/sha256/sha256.c +++ b/lib/sha256/sha256.c @@ -111,7 +111,7 @@ #endif #endif -#if defined(__APPLE__) && !defined(HAVE_ENDIAN_H) +#if defined(__APPLE__) #include #define be16toh(x) OSSwapBigToHostInt16((x)) #define htobe16(x) OSSwapHostToBigInt16((x)) @@ -119,7 +119,7 @@ #define be32toh(x) OSSwapBigToHostInt32((x)) #define htole32(x) OSSwapHostToLittleInt32(x) #define htobe32(x) OSSwapHostToBigInt32(x) -#endif /* __APPLE__ && !HAVE_ENDIAN_H */ +#endif /* __APPLE__ */ #if defined(_WIN32) && !defined(HAVE_ENDIAN_H) #include diff --git a/misc/ios/Info.plist.in b/misc/ios/Info.plist.in new file mode 100644 index 0000000000..791b2489a6 --- /dev/null +++ b/misc/ios/Info.plist.in @@ -0,0 +1,30 @@ + + + + + CFBundlePackageType + APPL + CFBundleDevelopmentRegion + English + CFBundleExecutable + luanti + CFBundleIconFile + luanti-icon.icns + CFBundleName + @PROJECT_NAME_CAPITALIZED@ + CFBundleDisplayName + @PROJECT_NAME_CAPITALIZED@ + CFBundleIdentifier + org.luanti.luanti + CFBundleVersion + @VERSION_STRING@ + CFBundleShortVersionString + @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@ + LSApplicationCategoryType + public.app-category.games + NSHighResolutionCapable + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/misc/ios/entitlements/release.entitlements b/misc/ios/entitlements/release.entitlements new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/misc/ios/entitlements/release.entitlements @@ -0,0 +1,6 @@ + + + + + + diff --git a/misc/macos/Info.plist.in b/misc/macos/Info.plist.in index 0cb03cab87..81531ce219 100644 --- a/misc/macos/Info.plist.in +++ b/misc/macos/Info.plist.in @@ -18,6 +18,8 @@ org.luanti.luanti CFBundleVersion @VERSION_STRING@ + CFBundleShortVersionString + @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@ LSApplicationCategoryType public.app-category.games NSHighResolutionCapable diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0813a11dc7..ef5fc38834 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -492,6 +492,10 @@ if(ANDROID) list(APPEND common_SRCS porting_android.cpp) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + list(APPEND common_SRCS porting_ios.mm) +endif() + if(BUILD_UNITTESTS) add_subdirectory(unittest) list(APPEND common_SRCS ${UNITTEST_SRCS}) @@ -626,10 +630,17 @@ endif() if(APPLE) # Configure the Info.plist file - configure_file( - "${CMAKE_SOURCE_DIR}/misc/macos/Info.plist.in" - "${CMAKE_BINARY_DIR}/Info.plist" - ) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") + configure_file( + "${CMAKE_SOURCE_DIR}/misc/macos/Info.plist.in" + "${CMAKE_BINARY_DIR}/Info.plist" + ) + else() + configure_file( + "${CMAKE_SOURCE_DIR}/misc/ios/Info.plist.in" + "${CMAKE_BINARY_DIR}/Info.plist" + ) + endif() endif() if(BUILD_CLIENT) @@ -704,6 +715,9 @@ if(BUILD_CLIENT) # on Android, Luanti depends on SDL2 directly # on other platforms, only IrrlichtMt depends on SDL2 "$<$:${SDL2_LIBRARIES}>" + #"$<$:-framework UIKit -framework Foundation -framework CoreGraphics -framework QuartzCore -framework AudioToolbox -framework AVFoundation -framework CoreAudio -framework CoreMotion -framework Security>" + "$<$:-framework Security -framework CoreHaptics -framework CoreBluetooth -framework GameController -framework Metal -framework Foundation>" + "$<$:${OPENGLES2_LIBRARY}>" ) target_compile_definitions(${PROJECT_NAME} PRIVATE "MT_BUILDTARGET=1") if(NOT USE_LUAJIT) diff --git a/src/main.cpp b/src/main.cpp index 2800dcd6df..d7645d4254 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,10 @@ extern "C" { #endif } +#if TARGET_OS_IPHONE +#include +#endif + #if !defined(__cpp_rtti) || !defined(__cpp_exceptions) #error Luanti cannot be built without exceptions or RTTI #endif diff --git a/src/porting.cpp b/src/porting.cpp index 711b65db69..f74bef66bc 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -38,11 +38,15 @@ #include #endif #if defined(__APPLE__) + #include #include #include // For _NSGetEnviron() // Related: https://gitlab.haskell.org/ghc/ghc/issues/2458 #include + #if TARGET_OS_IPHONE + #include "porting_ios.h" + #endif #endif #if defined(__HAIKU__) @@ -538,6 +542,7 @@ bool setSystemPaths() } CFRelease(resources_url); +#if TARGET_OS_MAC const char *const minetest_user_path = getenv("MINETEST_USER_PATH"); if (minetest_user_path && minetest_user_path[0] != '\0') { path_user = std::string(minetest_user_path); @@ -547,6 +552,11 @@ bool setSystemPaths() + "/Library/Application Support/" + "minetest"; } +#elif TARGET_OS_IPHONE + path_user = getAppleDocumentsDirectory(); +#else + #error "Not supported Apple OS." +#endif return true; } diff --git a/src/porting_ios.h b/src/porting_ios.h new file mode 100644 index 0000000000..0fa2888398 --- /dev/null +++ b/src/porting_ios.h @@ -0,0 +1,8 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 SFENCE + +#include + +std::string getAppleDocumentsDirectory(); +std::string getAppleLibraryDirectory(); diff --git a/src/porting_ios.mm b/src/porting_ios.mm new file mode 100644 index 0000000000..16d6e50fb1 --- /dev/null +++ b/src/porting_ios.mm @@ -0,0 +1,17 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 SFENCE + +#include "porting_ios.h" + +#import + +std::string getAppleDocumentsDirectory() { + NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; + return std::string([documentsDirectory UTF8String]); +} + +std::string getAppleLibraryDirectory() { + NSString *libraryDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Library"]; + return std::string([libraryDirectory UTF8String]); +} diff --git a/src/util/serialize.h b/src/util/serialize.h index 7da5f44d61..051eb47c55 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -38,6 +38,7 @@ #elif !defined(BYTE_ORDER) && defined(__BYTE_ORDER) #define BYTE_ORDER __BYTE_ORDER #endif +#undef HAVE_ENDIAN_H #define FIXEDPOINT_FACTOR 1000.0f diff --git a/util/ci/build_ios.sh b/util/ci/build_ios.sh new file mode 100755 index 0000000000..8b661f0921 --- /dev/null +++ b/util/ci/build_ios.sh @@ -0,0 +1,49 @@ +#!/bin/bash -e + +sudo xcode-select -s /Applications/Xcode_${xcodever}.app/Contents/Developer +sdkroot="$(realpath $(xcrun --sdk iphonesimulator --show-sdk-path)/../iPhoneSimulator${osver}.sdk)" +export CMAKE_PREFIX_PATH=${DEPS_DIR} +export SDKROOT="$sdkroot" + +cmake .. -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=$osver -DCMAKE_FIND_FRAMEWORK=LAST -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_INSTALL_PREFIX=../build/ios/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=${DEPS_DIR} \ + -DENABLE_OPENGL=OFF \ + -DENABLE_OPENGL3=OFF \ + -DENABLE_GLES2=ON \ + -DUSE_SDL2=ON \ + -DSDL2_LIBRARIES="${DEPS_DIR}/lib/libSDL2.a;${DEPS_DIR}/lib/libSDL2main.a" \ + -DSDL2_INCLUDE_DIRS=${DEPS_DIR}/include/SDL2 \ + -DOPENGLES2_LIBRARY=${DEPS_DIR}/lib/libGLESv2_static.a \ + -DOPENGLES2_INCLUDE_DIR=${DEPS_DIR}/include/ANGLE \ + -DCURL_LIBRARY=${DEPS_DIR}/lib/libcurl.a \ + -DCURL_INCLUDE_DIR=${DEPS_DIR}/include \ + -DFREETYPE_LIBRARY=${DEPS_DIR}/lib/libfreetype.a \ + -DFREETYPE_INCLUDE_DIRS=${DEPS_DIR}/include/freetype2 \ + -DGETTEXT_INCLUDE_DIR=${DEPS_DIR}/include \ + -DGETTEXT_LIBRARY=${DEPS_DIR}/lib/libintl.a \ + -DLUA_LIBRARY=${DEPS_DIR}/lib/libluajit-5.1.a \ + -DLUA_INCLUDE_DIR=${DEPS_DIR}/include/luajit-2.1 \ + -DOGG_LIBRARY=${DEPS_DIR}/lib/libogg.a \ + -DOGG_INCLUDE_DIR=${DEPS_DIR}/include \ + -DVORBIS_LIBRARY=${DEPS_DIR}/lib/libvorbis.a \ + -DVORBISFILE_LIBRARY=${DEPS_DIR}/lib/libvorbisfile.a \ + -DVORBIS_INCLUDE_DIR=${DEPS_DIR}/include \ + -DZSTD_LIBRARY=${DEPS_DIR}/lib/libzstd.a \ + -DZSTD_INCLUDE_DIR=${DEPS_DIR}/include \ + -DGMP_LIBRARY=${DEPS_DIR}/lib/libgmp.a \ + -DGMP_INCLUDE_DIR=${DEPS_DIR}/include \ + -DJSON_LIBRARY=${DEPS_DIR}/lib/libjsoncpp.a \ + -DJSON_INCLUDE_DIR=${DEPS_DIR}/include \ + -DENABLE_LEVELDB=OFF \ + -DENABLE_POSTGRESQL=OFF \ + -DENABLE_REDIS=OFF \ + -DJPEG_LIBRARY=${DEPS_DIR}/lib/libjpeg.a \ + -DJPEG_INCLUDE_DIR=${DEPS_DIR}/include \ + -DPNG_LIBRARY=${DEPS_DIR}/lib/libpng.a \ + -DPNG_PNG_INCLUDE_DIR=${DEPS_DIR}/include \ + -DCMAKE_EXE_LINKER_FLAGS="-lbz2 ${DEPS_DIR}/lib/libANGLE_static.a ${DEPS_DIR}/lib/libEGL_static.a" \ + -DXCODE_CODE_SIGN_ENTITLEMENTS=${REPDIR}/misc/ios/entitlements/release.entitlements \ + -GXcode +xcodebuild -project luanti.xcodeproj -scheme luanti -configuration Release build + diff --git a/util/ci/common.sh b/util/ci/common.sh index bd653a2c82..d8b403387a 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -49,3 +49,22 @@ install_macos_deps() { brew unlink $(brew ls --formula) brew link "${pkgs[@]}" } + +# iOS build only +install_ios_deps() { + osver=$1 + + local pkgs=( + cmake gettext wget + ) + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + export HOMEBREW_NO_INSTALL_CLEANUP=1 + # contrary to how it may look --auto-update makes brew do *less* + brew update --auto-update + brew install --display-times "${pkgs[@]}" + brew unlink $(brew ls --formula) + brew link "${pkgs[@]}" + + wget ios${osver}_deps.tar.gz https://github.com/luanti-org/luanti_ios_deps/releases/download/latest/ios${osver}_deps.tar.gz || echo "Ignore stupid error number 4: $?" + tar -xf ios${osver}_deps.tar.gz +} diff --git a/util/ci/run_ios.sh b/util/ci/run_ios.sh new file mode 100755 index 0000000000..a9c314dba7 --- /dev/null +++ b/util/ci/run_ios.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +DEVICE_NAME=$1 +TIMEOUT=$2 + +xcrun simctl boot "$DEVICE_NAME" +xcrun simctl install booted build/build/Release-iphonesimulator/luanti.app + +# Run the iOS app in the background +xcrun simctl launch --console booted org.luanti.luanti --run-unittests 2> log.txt & +APP_PID=$! + +# Initialize variables +CHECK_INTERVAL=15 +ELAPSED_TIME=0 +FOUND_RESULT=false + +# Monitor the log file +while [ $ELAPSED_TIME -lt $TIMEOUT ]; do + if grep -q "Unit Test Results:" log.txt; then + FOUND_RESULT=true + break + fi + sleep $CHECK_INTERVAL + ELAPSED_TIME=$((ELAPSED_TIME + CHECK_INTERVAL)) +done + +# Terminate the app +if $FOUND_RESULT; then + echo "Unit test results found. Terminating the app." +else + echo "Timeout reached. Terminating the app." +fi +xcrun simctl terminate booted org.luanti.luanti +xcrun simctl shutdown booted +