{"id":366,"date":"2021-11-30T03:18:09","date_gmt":"2021-11-30T03:18:09","guid":{"rendered":"https:\/\/writingagame.com\/?p=366"},"modified":"2021-12-15T07:45:50","modified_gmt":"2021-12-15T07:45:50","slug":"chapter-6-cross-platform-android","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/11\/30\/chapter-6-cross-platform-android\/","title":{"rendered":"Chapter 6. Cross-platform, Android"},"content":{"rendered":"\n<p>Now we will try to run <em>TheGame <\/em>spinning triangle sample on Android.<\/p>\n\n\n\n<p>1. Start Visual Studio, open <em>C:\\CPP\\a999hello\\p_android\\p_android.sln<\/em> solution.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2. Under <em>p_android.NativeActivity<\/em> project add \u201cxTheGame\u201d filter:<\/p>\n\n\n\n<p>Right-click on  <em>p_android.NativeActivity<\/em>  project -&gt; <em>Add -&gt; New <strong>Filter<\/strong><\/em>. Name &#8211; <strong>xTheGame<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<ol class=\"wp-block-list\" start=\"3\"><li>Under <em>xTheGame <\/em>add Existing Item:<\/li><\/ol>\n\n\n\n<p>Right-click on <em>xTheGame -&gt; Add -&gt; <strong>Existing <\/strong>Item<\/em>,<\/p>\n\n\n\n<p>Navigate to <em>C:\\CPP\\a999hello<\/em><\/p>\n\n\n\n<p>Files \u2013 <em>TheGame.cpp<\/em> and <em>TheGame.h<\/em><\/p>\n\n\n\n<p>Add.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<ol class=\"wp-block-list\" start=\"4\"><li>Notify <em>p_android.NativeActivity<\/em> project where to look for<em> TheGame.h<\/em>.<\/li><\/ol>\n\n\n\n<p>Right-click on  <em style=\"font-size: revert;\">p_android.NativeActivity<\/em><span style=\"font-size: revert;\"> <\/span> project -&gt; Properties, <strong>All Configurations<\/strong>, ARM64, <em>C\/C++ -&gt; General-&gt; Additional Include Directories -&gt; Edit<\/em>, add new line.<\/p>\n\n\n\n<p><strong>Attention<\/strong>: this time instead of navigating to <em>C:\\CPP\\a999hello<\/em> (where <em>TheGame <\/em>is actually located), manually add<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>..\/..<\/code><\/pre>\n\n\n\n<p>2 dots, slash, 2 dots, which means TWO folder levels up (from  <em style=\"font-size: revert;\">p_android.NativeActivity<\/em><span style=\"font-size: revert;\"> <\/span> project root).<\/p>\n\n\n\n<p>Ok, Apply, Ok.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<ol class=\"wp-block-list\" start=\"5\"><li>Add an access to <em>linmath.h<\/em> (required by <em>TheGame<\/em> class).<\/li><\/ol>\n\n\n\n<p>The file is located in <em>C:\/CPP\/engine<\/em> folder.<\/p>\n\n\n\n<p>Add new <strong>filter <\/strong>under <em>p_android.NativeActivity<\/em> project, name &#8211; <strong>xEngine<\/strong>. <\/p>\n\n\n\n<p>Under <em>xEngine <\/em>add <strong>Existing <\/strong>Item <em>C:\\CPP\\engine\\linmath.h<\/em><\/p>\n\n\n\n<p>Add reference: right-click on  <em>p_android.NativeActivity<\/em>  project -&gt; Properties, <strong>All Configurations<\/strong>, ARM64, <em>C\/C++ -&gt; General, Additional Include Directories -&gt; Edit<\/em>, add new line, navigate to <em>C:\\CPP\\engine<\/em>, Select Folder, Ok, Apply, Ok.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>6. Move Android implementations of <em>myPollEvents <\/em>and <em>mySwapBuffers<\/em> to <em>platform.h\/cpp<\/em>. Will move <em>mylog()<\/em> to <em>.cpp<\/em> too.<\/p>\n\n\n\n<p>Open <em>platform.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n\nvoid mylog(const char* _Format, ...);\nvoid mySwapBuffers();\nvoid myPollEvents();\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>7. Under <em>xPlatform <\/em>add new <strong>C++<\/strong> file, <\/p>\n\n\n\n<p>Name &#8211; <em>platform.cpp<\/em><\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\p_android\\<\/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#include &lt;android\/log.h&gt;\n#include &quot;stdio.h&quot;\n#include &quot;TheGame.h&quot;\n\nextern struct android_app* androidApp;\nextern const ASensor* accelerometerSensor;\nextern ASensorEventQueue* sensorEventQueue;\n\nextern EGLDisplay androidDisplay;\nextern EGLSurface androidSurface;\nextern TheGame theGame;\n\nvoid mylog(const char* _Format, ...) {\n#ifdef _DEBUG\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#endif\n};\n\nvoid mySwapBuffers() {\n\teglSwapBuffers(androidDisplay, androidSurface);\n}\nvoid myPollEvents() {\n\t\/\/ Read all pending events.\n\tint ident;\n\tint events;\n\tstruct android_poll_source* source;\n\n\t\/\/ If not animating, we will block forever waiting for events.\n\t\/\/ If animating, we loop until all events are read, then continue\n\t\/\/ to draw the next frame of animation.\n\twhile ((ident = ALooper_pollAll(0, NULL, &amp;events,\n\t\t(void**)&amp;source)) &gt;= 0) {\n\n\t\t\/\/ Process this event.\n\t\tif (source != NULL) {\n\t\t\tsource-&gt;process(androidApp, source);\n\t\t}\n\n\t\t\/\/ If a sensor has data, process it now.\n\t\tif (ident == LOOPER_ID_USER) {\n\t\t\tif (accelerometerSensor != NULL) {\n\t\t\t\tASensorEvent event;\n\t\t\t\twhile (ASensorEventQueue_getEvents(sensorEventQueue,\n\t\t\t\t\t&amp;event, 1) &gt; 0) {\n\t\t\t\t\t\/\/LOGI(&quot;accelerometer: x=%f y=%f z=%f&quot;,\n\t\t\t\t\t\/\/\tevent.acceleration.x, event.acceleration.y,\n\t\t\t\t\t\/\/\tevent.acceleration.z);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t\/\/ Check if we are exiting.\n\t\tif (androidApp-&gt;destroyRequested != 0) {\n\t\t\ttheGame.bExitGame = true;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>8. Change <em>main.cpp<\/em> to call <em>TheGame <\/em>class instead of drawing green flashing screen.<\/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: [2,4,59,60,61,62,63,64,196,198]; title: ; notranslate\" title=\"\">\n#include &quot;platform.h&quot;\n#include &quot;TheGame.h&quot;\n\nTheGame theGame;\n\nstruct android_app* androidApp;\n\nASensorManager* sensorManager;\nconst ASensor* accelerometerSensor;\nASensorEventQueue* sensorEventQueue;\n\nEGLDisplay androidDisplay;\nEGLSurface androidSurface;\nEGLContext androidContext;\n\n\/**\n* Initialize an EGL context for the current display.\n*\/\nstatic int engine_init_display(struct engine* engine) {\n\t\/\/ initialize OpenGL ES and EGL\n\n\t\/*\n\t* Here specify the attributes of the desired configuration.\n\t* Below, we select an EGLConfig with at least 8 bits per color\n\t* component compatible with on-screen windows\n\t*\/\n\tconst EGLint attribs&#91;] = {\n\t\tEGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n\t\tEGL_BLUE_SIZE, 8,\n\t\tEGL_GREEN_SIZE, 8,\n\t\tEGL_RED_SIZE, 8,\n\t\tEGL_NONE\n\t};\n\tEGLint format;\n\tEGLint numConfigs;\n\tEGLConfig config;\n\tEGLSurface surface;\n\tEGLContext context;\n\n\tEGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n\n\teglInitialize(display, 0, 0);\n\n\t\/* Here, the application chooses the configuration it desires. In this\n\t* sample, we have a very simplified selection process, where we pick\n\t* the first EGLConfig that matches our criteria *\/\n\teglChooseConfig(display, attribs, &amp;config, 1, &amp;numConfigs);\n\n\t\/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is\n\t* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().\n\t* As soon as we picked a EGLConfig, we can safely reconfigure the\n\t* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. *\/\n\teglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &amp;format);\n\n\tANativeWindow_setBuffersGeometry(androidApp-&gt;window, 0, 0, format);\n\n\tsurface = eglCreateWindowSurface(display, config, androidApp-&gt;window, NULL);\n\n\tEGLint contextAttribs&#91;] =\n\t{\n\t\tEGL_CONTEXT_CLIENT_VERSION, 3,\n\t\tEGL_NONE\n\t};\n\tcontext = eglCreateContext(display, config, NULL, contextAttribs);\n\n\n\tif (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {\n\t\tmylog(&quot;ERROR: Unable to eglMakeCurrent&quot;);\n\t\treturn -1;\n\t}\n\n\tandroidDisplay = display;\n\tandroidContext = context;\n\tandroidSurface = surface;\n\n\t\/\/ Initialize GL state.\n\tglEnable(GL_CULL_FACE);\n\tglDisable(GL_DEPTH_TEST);\n\n\treturn 0;\n}\n\n\/**\n* Tear down the EGL context currently associated with the display.\n*\/\nstatic void engine_term_display() {\n\n\tif (androidDisplay != EGL_NO_DISPLAY) {\n\t\teglMakeCurrent(androidDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n\t\tif (androidContext != EGL_NO_CONTEXT) {\n\t\t\teglDestroyContext(androidDisplay, androidContext);\n\t\t}\n\t\tif (androidSurface != EGL_NO_SURFACE) {\n\t\t\teglDestroySurface(androidDisplay, androidSurface);\n\t\t}\n\t\teglTerminate(androidDisplay);\n\t}\n\tandroidDisplay = EGL_NO_DISPLAY;\n\tandroidContext = EGL_NO_CONTEXT;\n\tandroidSurface = EGL_NO_SURFACE;\n}\n\n\/**\n* Process the next input event.\n*\/\nstatic int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {\n\tif (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {\n\t\t\/\/engine-&gt;state.x = AMotionEvent_getX(event, 0);\n\t\t\/\/engine-&gt;state.y = AMotionEvent_getY(event, 0);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n\/**\n* Process the next main command.\n*\/\nstatic void engine_handle_cmd(struct android_app* app, int32_t cmd) {\n\tstruct engine* engine = (struct engine*)app-&gt;userData;\n\tswitch (cmd) {\n\tcase APP_CMD_INIT_WINDOW:\n\t\t\/\/ The window is being shown, get it ready.\n\t\tif (androidApp-&gt;window != NULL) {\n\t\t\tengine_init_display(engine);\n\t\t\t\/\/engine_draw_frame(engine);\n\t\t}\n\t\tbreak;\n\tcase APP_CMD_TERM_WINDOW:\n\t\t\/\/ The window is being hidden or closed, clean it up.\n\t\tengine_term_display();\n\t\tbreak;\n\tcase APP_CMD_GAINED_FOCUS:\n\t\t\/\/ When our app gains focus, we start monitoring the accelerometer.\n\t\tif (accelerometerSensor != NULL) {\n\t\t\tASensorEventQueue_enableSensor(sensorEventQueue,\n\t\t\t\taccelerometerSensor);\n\t\t\t\/\/ We&#039;d like to get 60 events per second (in microseconds).\n\t\t\tASensorEventQueue_setEventRate(sensorEventQueue,\n\t\t\t\taccelerometerSensor, (1000L \/ 60) * 1000);\n\t\t}\n\t\tbreak;\n\tcase APP_CMD_LOST_FOCUS:\n\t\t\/\/ When our app loses focus, we stop monitoring the accelerometer.\n\t\t\/\/ This is to avoid consuming battery while not being used.\n\t\tif (accelerometerSensor != NULL) {\n\t\t\tASensorEventQueue_disableSensor(sensorEventQueue,\n\t\t\t\taccelerometerSensor);\n\t\t}\n\t\t\/\/ Also stop animating.\n\t\t\/\/engine_draw_frame(engine);\n\t\tbreak;\n\t}\n}\n\n\/**\n* This is the main entry point of a native application that is using\n* android_native_app_glue.  It runs in its own thread, with its own\n* event loop for receiving input events and doing other things.\n*\/\nvoid android_main(struct android_app* state) {\n\n\t\/\/state-&gt;userData = &amp;engine;\n\tstate-&gt;onAppCmd = engine_handle_cmd;\n\tstate-&gt;onInputEvent = engine_handle_input;\n\tandroidApp = state;\n\n\t\/\/ Prepare to monitor accelerometer\n\tsensorManager = ASensorManager_getInstance();\n\taccelerometerSensor = ASensorManager_getDefaultSensor(sensorManager,\n\t\tASENSOR_TYPE_ACCELEROMETER);\n\tsensorEventQueue = ASensorManager_createEventQueue(sensorManager,\n\t\tstate-&gt;looper, LOOPER_ID_USER, NULL, NULL);\n\n\t\/\/ Read all pending events.\n\tint ident;\n\tint events;\n\tstruct android_poll_source* source;\n\t\/\/wait for display\n\twhile (androidDisplay == NULL) {\n\t\t\/\/ No display yet.\n\t\t\/\/std::this_thread::sleep_for(std::chrono::seconds(1));\n\t\tmylog(&quot;No display yet\\n&quot;);\n\t\t\/\/wait for event\n\t\twhile ((ident = ALooper_pollAll(0, NULL, &amp;events,\n\t\t\t(void**)&amp;source)) &gt;= 0) {\n\t\t\t\/\/ Process this event.\n\t\t\tif (source != NULL) {\n\t\t\t\tsource-&gt;process(state, source);\n\t\t\t}\n\t\t}\n\t}\n\n\tEGLint w, h;\n\teglQuerySurface(androidDisplay, androidSurface, EGL_WIDTH, &amp;w);\n\teglQuerySurface(androidDisplay, androidSurface, EGL_HEIGHT, &amp;h);\n\ttheGame.onScreenResize(w, h);\n\n\ttheGame.run();\n\n\tengine_term_display();\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Have 1 more 3.2-related change (<em>eglCreateContext<\/em>, highlighted too).<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9. In current configuration the builder won&#8217;t find math libraries. Need to build with <strong>-lm<\/strong> key.<\/p>\n\n\n\n<p>Open <em>p_android.NativeActivity<\/em> properties, All Configurations, ARM64, proceed to <em>Configuration Properties -&gt; Linker -&gt; All Options<\/em>, open <em>Additional Options -&gt; Edit<\/em>, add<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-lm<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Ok, Apply, Ok.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>10. One more <em>decorative <\/em>touch for the sake of consistency with Windows project:<\/p>\n\n\n\n<p> Under <em>p_android.NativeActivity<\/em> project add new filter <strong>Source Files<\/strong><\/p>\n\n\n\n<p>In <em>Solution Explorer<\/em> (right-side panel) drag-and-drop original source files to  <strong>Source Files<\/strong>:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c06\/01.jpg\"><\/p>\n\n\n\n<p>So, now structure is:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c06\/02.jpg\"><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>11. Switch on Android, <strong>unlock<\/strong>, plug in to PC with USB cable, allow debugging, build and run:<\/p>\n\n\n\n<p>Ta-da!<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c06\/03.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Runs BOTH on Android AND Windows!<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c06\/04.jpg\"><\/p>\n\n\n\n<p>Now we CAN say that we DO have a real cross-platform solution!<\/p>\n\n\n\n<p> <em>VS top menu -&gt; Debug -&gt; Stop Debugging.<\/em> <\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Now we will try to run TheGame spinning triangle sample on Android. 1. Start Visual Studio, open C:\\CPP\\a999hello\\p_android\\p_android.sln solution. 2. Under p_android.NativeActivity project add \u201cxTheGame\u201d filter: Right-click on p_android.NativeActivity project -&gt; Add -&gt; New Filter. Name &#8211; xTheGame Under xTheGame add Existing Item: Right-click on xTheGame -&gt; Add -&gt; Existing Item, Navigate to C:\\CPP\\a999hello Files [&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-366","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\/366","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=366"}],"version-history":[{"count":16,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/366\/revisions"}],"predecessor-version":[{"id":962,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/366\/revisions\/962"}],"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=366"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=366"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=366"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}