{"id":1900,"date":"2023-05-28T21:59:49","date_gmt":"2023-05-28T21:59:49","guid":{"rendered":"https:\/\/writingagame.com\/?p=1900"},"modified":"2026-04-08T17:01:39","modified_gmt":"2026-04-08T17:01:39","slug":"chapter-5-hello-cross-platform","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2023\/05\/28\/chapter-5-hello-cross-platform\/","title":{"rendered":"Chapter 5. Hello Cross-platform!!"},"content":{"rendered":"\n<p><strong>Tags:<\/strong> <em>Cross-Platform Success, Android NDK, CMake, OpenGL ES 3.2, C++ Core, Mobile &amp; Desktop, Software Architecture<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Now we will try to run <em>TheApp <\/em>spinning triangle sample on Android. For this we&#8217;ll need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>provide <em>\/p_android\/platform.cpp\/h<\/em> implementation<\/li>\n\n\n\n<li>and replace flashing green screen code in <em>main.cpp<\/em> by <em>theApp.run()<\/em> call<\/li>\n<\/ul>\n\n\n\n<p>Didn&#8217;t find how to add classes\/files outside of <em>cpp<\/em> folder in Android Studio, so let&#8217;s do it manually.<\/p>\n\n\n\n<p>1. In <strong>Windows File Explorer<\/strong> create a new folder<\/p>\n\n\n\n<p><em>C:\/CPP\/<strong>p_android<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>2. In a <strong>text editor<\/strong> (I use <a rel=\"noreferrer noopener\" href=\"https:\/\/notepad-plus-plus.org\/\" target=\"_blank\">Notepad++<\/a>)  create a file<\/p>\n\n\n\n<p><em>C:\/CPP\/p_android\/<strong>platform.h<\/strong><\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;GLES3\/gl32.h&gt;\nvoid mylog(const char* _Format, ...);\nvoid mySwapBuffers();\nvoid myPollEvents();\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>3.  <span style=\"font-size: revert;\"> In a text editor create a file<\/span><\/p>\n\n\n\n<p><em>C:\/CPP\/p_android\/<strong>platform.cpp<\/strong><\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &lt;android\/log.h&gt;\n#include &quot;stdio.h&quot;\n#include &quot;TheApp.h&quot;\n#include &lt;EGL\/egl.h&gt;\n#include &lt;game-activity\/native_app_glue\/android_native_app_glue.h&gt;\nextern struct android_app* pAndroidApp;\nextern EGLDisplay androidDisplay;\nextern EGLSurface androidSurface;\nextern TheApp theApp;\n \nvoid mylog(const char* _Format, ...) {\n    char outStr&#91;1024];\n    va_list _ArgList;\n    va_start(_ArgList, _Format);\n    vsprintf(outStr, _Format, _ArgList);\n    __android_log_print(ANDROID_LOG_INFO, &quot;mylog&quot;, outStr, NULL);\n    va_end(_ArgList);\n};\n \nvoid mySwapBuffers() {\n    eglSwapBuffers(androidDisplay, androidSurface);\n}\nvoid myPollEvents() {\n    \/\/ Process all pending events before running game logic.\n    int events;\n    android_poll_source *pSource;\n    if (ALooper_pollAll(0, nullptr, &amp;events, (void **) &amp;pSource) &gt;= 0)\n        if (pSource)\n            pSource-&gt;process(pAndroidApp, pSource);\n    \/\/if no display - wait for it\n    while (androidDisplay == EGL_NO_DISPLAY)\n        if (ALooper_pollAll(0, nullptr, &amp;events, (void **) &amp;pSource) &gt;= 0)\n            if (pSource)\n                pSource-&gt;process(pAndroidApp, pSource);\n    \/\/ handle all queued inputs\n    for (auto i = 0; i &lt; pAndroidApp-&gt;motionEventsCount; i++) {\n        \/\/ cache the current event\n        auto &amp;motionEvent = pAndroidApp-&gt;motionEvents&#91;i];\n        \/\/ cache the current action\n        auto action = motionEvent.action;\n        \/\/ Find the pointer index, mask and bitshift to turn it into a readable value\n        auto pointerIndex = (action &amp; AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)\n                &gt;&gt; AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;\n        \/\/aout &lt;&lt; &quot;Pointer &quot; &lt;&lt; pointerIndex &lt;&lt; &quot;:&quot;;\n        \/\/ get the x and y position of this event\n        auto &amp;pointer = motionEvent.pointers&#91;pointerIndex];\n        auto x = GameActivityPointerAxes_getX(&amp;pointer);\n        auto y = GameActivityPointerAxes_getY(&amp;pointer);\n        \/\/aout &lt;&lt; &quot;(&quot; &lt;&lt; x &lt;&lt; &quot;, &quot; &lt;&lt; y &lt;&lt; &quot;) &quot;;\n        \/\/ Only consider touchscreen events, like touches\n        auto actionMasked = action &amp; AINPUT_SOURCE_TOUCHSCREEN;\n        \/\/ determine the kind of event it is\n        switch (actionMasked) {\n            case AMOTION_EVENT_ACTION_DOWN:\n            case AMOTION_EVENT_ACTION_POINTER_DOWN:\n                \/\/aout &lt;&lt; &quot;Pointer Down&quot;;\n                break;\n            case AMOTION_EVENT_ACTION_UP:\n            case AMOTION_EVENT_ACTION_POINTER_UP:\n                \/\/aout &lt;&lt; &quot;Pointer Up&quot;;\n                break;\n            default:\n                ;\/\/aout &lt;&lt; &quot;Pointer Move&quot;;\n        }\n    }\n    android_app_clear_motion_events(pAndroidApp);\n    \/\/ handle key inputs\n    for (auto i = 0; i &lt; pAndroidApp-&gt;keyUpEventsCount; i++) {\n        \/\/ cache the current event\n        auto &amp;keyEvent = pAndroidApp-&gt;keyUpEvents&#91;i];\n        if (keyEvent.keyCode == AKEYCODE_BACK) {\n            \/\/ actions on back key\n            theApp.bExitApp = true;\n        }\n    }\n    android_app_clear_key_up_events(pAndroidApp);\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Please note: <em>mylog <\/em>implementation prints in Android Studio&#8217;s <strong>logcat <\/strong>window.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>4. Start Android Studio, <\/p>\n\n\n\n<p>open <em>C:\\CPP\\a999hello\\<strong>pa<\/strong><\/em> project.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>5. Replace flashing green screen code by <em>theGame.run()<\/em> call.<\/p>\n\n\n\n<p>Open <em>main.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [9,11,130,132]; title: ; notranslate\" title=\"\">\n#include &quot;platform.h&quot;\n#include &lt;jni.h&gt;\n#include &lt;EGL\/egl.h&gt;\n#include &lt;game-activity\/GameActivity.cpp&gt;\n#include &lt;game-text-input\/gametextinput.cpp&gt;\n#include &lt;game-activity\/native_app_glue\/android_native_app_glue.c&gt;\n#include &quot;TheApp.cpp&quot;\nTheApp theApp;\nstruct android_app* pAndroidApp = NULL;\nEGLDisplay androidDisplay = EGL_NO_DISPLAY;\nEGLSurface androidSurface = EGL_NO_SURFACE;\nEGLContext androidContext = EGL_NO_CONTEXT;\nbool bExitApp = false;\nint screenSize&#91;2] = {0,0};\nvoid android_init_display() {\n    \/\/ Choose your render attributes\n    constexpr EGLint attribs&#91;] = {\n            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,\n            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n            EGL_BLUE_SIZE, 8,\n            EGL_GREEN_SIZE, 8,\n            EGL_RED_SIZE, 8,\n            EGL_DEPTH_SIZE, 24,\n            EGL_NONE\n    };\n    \/\/ The default display is probably what you want on Android\n    auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n    eglInitialize(display, nullptr, nullptr);\n    \/\/ figure out how many configs there are\n    EGLint numConfigs;\n    eglChooseConfig(display, attribs, nullptr, 0, &amp;numConfigs);\n    \/\/ get the list of configurations\n    std::unique_ptr&lt;EGLConfig&#91;]&gt; supportedConfigs(new EGLConfig&#91;numConfigs]);\n    eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &amp;numConfigs);\n    \/\/ Find a config we like.\n    \/\/ Could likely just grab the first if we don&#039;t care about anything else in the config.\n    \/\/ Otherwise hook in your own heuristic\n    auto config = *std::find_if(\n            supportedConfigs.get(),\n            supportedConfigs.get() + numConfigs,\n            &#91;&amp;display](const EGLConfig &amp;config) {\n                EGLint red, green, blue, depth;\n                if (eglGetConfigAttrib(display, config, EGL_RED_SIZE, &amp;red)\n                    &amp;&amp; eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &amp;green)\n                    &amp;&amp; eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &amp;blue)\n                    &amp;&amp; eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &amp;depth)) {\n                    \/\/aout &lt;&lt; &quot;Found config with &quot; &lt;&lt; red &lt;&lt; &quot;, &quot; &lt;&lt; green &lt;&lt; &quot;, &quot; &lt;&lt; blue &lt;&lt; &quot;, &quot;\n                    \/\/     &lt;&lt; depth &lt;&lt; std::endl;\n                    return red == 8 &amp;&amp; green == 8 &amp;&amp; blue == 8 &amp;&amp; depth == 24;\n                }\n                return false;\n            });\n    \/\/ create the proper window surface\n    EGLint format;\n    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &amp;format);\n    EGLSurface surface = eglCreateWindowSurface(display, config, pAndroidApp-&gt;window, nullptr);\n    \/\/ Create a GLES 3 context\n    EGLint contextAttribs&#91;] = {\n            EGL_CONTEXT_MAJOR_VERSION, 3,\n            EGL_CONTEXT_MINOR_VERSION, 2,\n            EGL_NONE};\n    EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);\n    \/\/ get some window metrics\n    auto madeCurrent = eglMakeCurrent(display, surface, surface, context);\n    if(!madeCurrent) {\n        ;\n    }\n    androidDisplay = display;\n    androidSurface = surface;\n    androidContext = context;\n}\nvoid android_term_display() {\n    if (androidDisplay != EGL_NO_DISPLAY) {\n        eglMakeCurrent(androidDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n        if (androidContext != EGL_NO_CONTEXT) {\n            eglDestroyContext(androidDisplay, androidContext);\n            androidContext = EGL_NO_CONTEXT;\n        }\n        if (androidSurface != EGL_NO_SURFACE) {\n            eglDestroySurface(androidDisplay, androidSurface);\n            androidSurface = EGL_NO_SURFACE;\n        }\n        eglTerminate(androidDisplay);\n        androidDisplay = EGL_NO_DISPLAY;\n    }\n}\nvoid handle_cmd(android_app *pApp, int32_t cmd) {\n    switch (cmd) {\n        case APP_CMD_INIT_WINDOW:\n            android_init_display();\n            \/\/updateRenderArea\n            EGLint width,height;\n            eglQuerySurface(androidDisplay, androidSurface, EGL_WIDTH, &amp;width);\n            eglQuerySurface(androidDisplay, androidSurface, EGL_HEIGHT, &amp;height);\n            screenSize&#91;0] = 0;\n            screenSize&#91;1] = 0;\n            theApp.onScreenResize(width,height);\n            break;\n        case APP_CMD_TERM_WINDOW:\n            android_term_display();\n            break;\n        default:\n            break;\n    }\n}\n\/*!\n * This the main entry point for a native activity\n *\/\nvoid android_main(struct android_app *pApp) {\n    pAndroidApp = pApp;\n    \/\/ register an event handler for Android events\n    pApp-&gt;onAppCmd = handle_cmd;\n    myPollEvents(); \/\/this will wait for display initialization\n    theApp.run();\n    android_term_display();\n    std::terminate();\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>It remains only to add new classes\/files to the project. Unlike Visual Studio, here we will do this not in the project properties, but in CMakeLists.txt.<\/p>\n\n\n\n<p>6. Open <em>CMakeLists.txt<\/em> <\/p>\n\n\n\n<p>and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [17,18,19,28,31,32,33,34,35,36]; title: ; notranslate\" title=\"\">\n# For more information about using CMake with Android Studio, read the\n# documentation: https:\/\/d.android.com\/studio\/projects\/add-native-code.html\n# Sets the minimum version of CMake required to build the native library.\ncmake_minimum_required(VERSION 3.22.1)\n# Declares and names the project.\nproject(&quot;pa&quot;)\n# Creates and names a library, sets it as either STATIC\n# or SHARED, and provides the relative paths to its source code.\n# You can define multiple libraries, and CMake builds them for you.\n# Gradle automatically packages shared libraries with your APK.\nset(rootDir &quot;..\/..\/..\/..\/..\/&quot;)\nset(platform &quot;${rootDir}\/..\/p_android&quot;)\nset(engine &quot;${rootDir}\/..\/engine&quot;)\nadd_library( # Sets the name of the library.\n        pa\n        # Sets the library as a shared library.\n        SHARED\n        # Provides a relative path to your source file(s).\n        ${platform}\/platform.cpp\n        main.cpp\n        )\ntarget_include_directories(pa\n        PUBLIC\n        ${rootDir}\n        ${platform}\n        ${engine}\n        )\n# Searches for a specified prebuilt library and stores the path as a\n# variable. Because CMake includes system libraries in the search path by\n# default, you only need to specify the name of the public NDK library\n# you want to add. CMake verifies that the library exists before\n# completing its build.\nfind_library( # Sets the name of the path variable.\n        log-lib\n        # Specifies the name of the NDK library that\n        # you want CMake to locate.\n        log)\n# Searches for a package provided by the game activity dependency\nfind_package(game-activity REQUIRED CONFIG)\n# Specifies libraries CMake should link to your target library. You\n# can link multiple libraries, such as libraries you define in this\n# build script, prebuilt third-party libraries, or system libraries.\ntarget_link_libraries( # Specifies the target library.\n        pa\n        android\n        # The game activity\n        game-activity::game-activity\n        # EGL, required for configuring the display context\n        EGL\n        # GL ES 3, used for the sample renderer\n        GLESv3\n        # for AImageDecoder, to load images from resources\n        jnigraphics\n        # Links the target library to the log library\n        # included in the NDK.\n        ${log-lib})\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Please note, line 11: <em>CMakeLists.txt<\/em> file is physically located in <em>C:\\CPP\\a999hello\\pa\\app\\src\\main\\cpp<\/em> folder, FIVE folder levels down from project&#8217;s root in <em>C:\\CPP\\a999hello<\/em>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Just in case, I also added ES 3.2 requirement into the Manifest.<\/p>\n\n\n\n<p>7. Open<em> AndroidManifest.xml<\/em> <\/p>\n\n\n\n<p>and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; highlight: [5]; title: ; notranslate\" title=\"\">\n&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;manifest xmlns:android=&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;\n    xmlns:tools=&quot;http:\/\/schemas.android.com\/tools&quot;&gt;\n    &lt;uses-feature android:glEsVersion=&quot;0x00030002&quot; android:required=&quot;true&quot; \/&gt;\n    &lt;application\n        android:allowBackup=&quot;true&quot;\n        android:dataExtractionRules=&quot;@xml\/data_extraction_rules&quot;\n        android:fullBackupContent=&quot;@xml\/backup_rules&quot;\n        android:icon=&quot;@mipmap\/ic_launcher&quot;\n        android:label=&quot;Hello Android&quot;\n        android:supportsRtl=&quot;true&quot;\n        android:theme=&quot;@style\/Theme.Pa&quot;\n        tools:targetApi=&quot;31&quot;&gt;\n        &lt;activity\n            android:name=&quot;.MainActivity&quot;\n            android:exported=&quot;true&quot;&gt;\n            &lt;intent-filter&gt;\n                &lt;action android:name=&quot;android.intent.action.MAIN&quot; \/&gt;\n                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; \/&gt;\n            &lt;\/intent-filter&gt;\n            &lt;meta-data\n                android:name=&quot;android.app.lib_name&quot;\n                android:value=&quot;pa&quot; \/&gt;\n        &lt;\/activity&gt;\n    &lt;\/application&gt;\n&lt;\/manifest&gt;\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>8. Turn on your Android, unlock, plug in, allow debugging, wait for it to show up in Android Studio and RUN (green arrow).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Ta-da!!<\/h2>\n\n\n\n<p>Runs on BOTH Android AND Windows:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b04\/c05\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Now we can claim that we DO have a real cross-platform solution!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>Again, just in case, both Android and Windows cross-platform solutions are saved on&nbsp;<a rel=\"noreferrer noopener\" href=\"https:\/\/writingagame.com\/index.php\/2023\/05\/11\/github\/\" target=\"_blank\">GitHub<\/a>.<\/p>\n\n\n\n<p>Link:&nbsp;<a href=\"https:\/\/github.com\/bkantemir\/_wg_405\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/bkantemir\/_wg_405<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Tags: Cross-Platform Success, Android NDK, CMake, OpenGL ES 3.2, C++ Core, Mobile &amp; Desktop, Software Architecture Now we will try to run TheApp spinning triangle sample on Android. For this we&#8217;ll need: Didn&#8217;t find how to add classes\/files outside of cpp folder in Android Studio, so let&#8217;s do it manually. 1. In Windows File Explorer [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":373,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1900","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1900","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/comments?post=1900"}],"version-history":[{"count":44,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1900\/revisions"}],"predecessor-version":[{"id":4281,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1900\/revisions\/4281"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/373"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=1900"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=1900"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=1900"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}