diff --git a/ColorProgram.cpp b/ColorProgram.cpp index 4f2237a..9ff9d66 100644 --- a/ColorProgram.cpp +++ b/ColorProgram.cpp @@ -9,7 +9,11 @@ ColorProgram::ColorProgram() { //Compile vertex and fragment shaders using the convenient 'gl_compile_program' helper function: program = gl_compile_program( //vertex shader: +#ifdef __ANDROID__ + "#version 320 es\n" +#else "#version 330\n" +#endif "uniform mat4 OBJECT_TO_CLIP;\n" "in vec4 Position;\n" "in vec4 Color;\n" @@ -20,12 +24,20 @@ ColorProgram::ColorProgram() { "}\n" , //fragment shader: +#ifdef __ANDROID__ + "#version 320 es\n" + "precision highp int;\n" //overkill but saves re-writing the shader below + "precision highp float;\n" //similar +#else "#version 330\n" +#endif "in vec4 color;\n" "out vec4 fragColor;\n" "void main() {\n" " fragColor = color;\n" "}\n" + , + "ColorProgram" ); //As you can see above, adjacent strings in C/C++ are concatenated. // this is very useful for writing long shader programs inline. diff --git a/LitColorTextureProgram.cpp b/LitColorTextureProgram.cpp index 6bde8a7..ae1a767 100644 --- a/LitColorTextureProgram.cpp +++ b/LitColorTextureProgram.cpp @@ -47,7 +47,12 @@ LitColorTextureProgram::LitColorTextureProgram() { //Compile vertex and fragment shaders using the convenient 'gl_compile_program' helper function: program = gl_compile_program( //vertex shader: +#ifdef __ANDROID__ + "#version 320 es\n" +#else "#version 330\n" +#endif + "#line " STR(__LINE__) "\n" "uniform mat4 OBJECT_TO_CLIP;\n" "uniform mat4x3 OBJECT_TO_LIGHT;\n" "uniform mat3 NORMAL_TO_LIGHT;\n" @@ -68,7 +73,14 @@ LitColorTextureProgram::LitColorTextureProgram() { "}\n" , //fragment shader: +#ifdef __ANDROID__ + "#version 320 es\n" + "precision highp int;\n" //overkill but saves re-writing the shader below + "precision highp float;\n" //similar +#else "#version 330\n" +#endif + "#line " STR(__LINE__) "\n" "uniform sampler2D TEX;\n" "uniform int LIGHT_TYPE;\n" "uniform vec3 LIGHT_LOCATION;\n" @@ -105,6 +117,8 @@ LitColorTextureProgram::LitColorTextureProgram() { " vec4 albedo = texture(TEX, texCoord) * color;\n" " fragColor = vec4(e*albedo.rgb, albedo.a);\n" "}\n" + , + "LitColorTextureProgram" ); //As you can see above, adjacent strings in C/C++ are concatenated. // this is very useful for writing long shader programs inline. diff --git a/Maekfile.js b/Maekfile.js index 37f7122..dbccfc6 100644 --- a/Maekfile.js +++ b/Maekfile.js @@ -32,6 +32,7 @@ const game_sources = [ const common_sources = [ 'data_path.cpp', + 'asset_stream.cpp', 'PathFont.cpp', 'PathFont-font.cpp', 'DrawLines.cpp', @@ -216,7 +217,13 @@ const ovr_openxr_loader_so = maek.COPY('../ovr-openxr-sdk/OpenXR/Libs/Android/ar const libcpp_shared_so = maek.COPY(`${NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so`, 'objs/android/apk/lib/arm64-v8a/libc++_shared.so'); +//assets that are just stored in the apk as-is (no processing): +const android_assets = [ + maek.COPY('dist/hexapod.scene', 'objs/android/apk/assets/hexapod.scene'), + maek.COPY('dist/hexapod.pnct', 'objs/android/apk/assets/hexapod.pnct'), +]; +//icons which are processed before being stored into the apk: const android_icons = [ ]; for (const dpi of ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi']) { const inFile = `android/res/mipmap-${dpi}/gp-icon.png`; @@ -259,6 +266,7 @@ const package_apk_task = async () => { AAPT2, 'link', '-o', packaged_apk, '-I', `${ANDROID_PLATFORM}/android.jar`, + '-A', 'objs/android/apk/assets', '--manifest', 'android/AndroidManifest.xml', ...android_icons, '-v' //verbose output @@ -297,7 +305,7 @@ const package_apk_task = async () => { }; -package_apk_task.depends = [android_game_so, ovr_openxr_loader_so, libcpp_shared_so, ...android_icons]; +package_apk_task.depends = [android_game_so, ovr_openxr_loader_so, libcpp_shared_so, ...android_icons, ...android_assets]; package_apk_task.label = `PACKAGE ${android_apk}`; maek.tasks[android_apk] = package_apk_task; diff --git a/Mesh.cpp b/Mesh.cpp index 9d67f62..4982e05 100644 --- a/Mesh.cpp +++ b/Mesh.cpp @@ -1,4 +1,5 @@ #include "Mesh.hpp" +#include "asset_stream.hpp" #include "read_write_chunk.hpp" #include @@ -14,7 +15,8 @@ MeshBuffer::MeshBuffer(std::string const &filename) { glGenBuffers(1, &buffer); - std::ifstream file(filename, std::ios::binary); + std::unique_ptr< std::istream > file_str = asset_stream(filename); + std::istream &file = *file_str; GLuint total = 0; diff --git a/PlayMode.cpp b/PlayMode.cpp index dcc01fd..5a756ed 100644 --- a/PlayMode.cpp +++ b/PlayMode.cpp @@ -173,8 +173,6 @@ void PlayMode::update(float elapsed) { } void PlayMode::draw(glm::uvec2 const &drawable_size) { - //update window's camera aspect ratio for drawable: - camera->aspect = float(drawable_size.x) / float(drawable_size.y); //set up light type and position for lit_color_texture_program: // TODO: consider using the Light(s) in the scene to do this @@ -184,6 +182,12 @@ void PlayMode::draw(glm::uvec2 const &drawable_size) { glUniform3fv(lit_color_texture_program->LIGHT_ENERGY_vec3, 1, glm::value_ptr(glm::vec3(1.0f, 1.0f, 0.95f))); glUseProgram(0); + //NOTE: on android, only render to swapchain images (below), not to the main window: + #ifndef __ANDROID__ + + //update window's camera aspect ratio for drawable: + camera->aspect = float(drawable_size.x) / float(drawable_size.y); + //main scene drawing into the window: draw_helper(camera->make_projection() * glm::mat4(camera->transform->make_world_to_local())); @@ -209,6 +213,8 @@ void PlayMode::draw(glm::uvec2 const &drawable_size) { glm::u8vec4(0xff, 0xff, 0xff, 0x00)); } + #endif //__ANDROID__ + //---------------------------------------------- if (xr && xr->next_frame.should_render) { @@ -251,6 +257,7 @@ void PlayMode::draw(glm::uvec2 const &drawable_size) { } } + GL_ERRORS(); } void PlayMode::draw_helper(glm::mat4 const &world_to_clip) { diff --git a/Scene.cpp b/Scene.cpp index 636ca0b..68bb6a2 100644 --- a/Scene.cpp +++ b/Scene.cpp @@ -2,10 +2,10 @@ #include "gl_errors.hpp" #include "read_write_chunk.hpp" +#include "asset_stream.hpp" #include -#include //------------------------- @@ -169,7 +169,8 @@ void Scene::draw(glm::mat4 const &world_to_clip, glm::mat4x3 const &world_to_lig void Scene::load(std::string const &filename, std::function< void(Scene &, Transform *, std::string const &) > const &on_drawable) { - std::ifstream file(filename, std::ios::binary); + std::unique_ptr< std::istream > file_ptr = asset_stream(filename); + auto &file = *file_ptr; std::vector< char > names; read_chunk(file, "str0", &names); diff --git a/XR.cpp b/XR.cpp index 97c38b3..4a104eb 100644 --- a/XR.cpp +++ b/XR.cpp @@ -420,7 +420,7 @@ XR::XR( //allocate framebuffer: glGenFramebuffers(1, &view.framebuffers[i].fb); glBindFramebuffer(GL_FRAMEBUFFER, view.framebuffers[i].fb); - //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, view.framebuffers[i].depth_rb); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, view.framebuffers[i].depth_rb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, view.framebuffers[i].color_tex, 0); GL_ERRORS(); @@ -729,7 +729,9 @@ void XR::end_frame() { } XrCompositionLayerProjection layer{XR_TYPE_COMPOSITION_LAYER_PROJECTION}; - layer.layerFlags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT; //note: "planned for deprecation" + layer.layerFlags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT //note: "planned for deprecation" + //| XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT //probably not needed for OPAQUE mode? + ; layer.space = stage; layer.viewCount = projection_views.size(); layer.views = projection_views.data(); diff --git a/asset_stream.cpp b/asset_stream.cpp new file mode 100644 index 0000000..c4bbaa8 --- /dev/null +++ b/asset_stream.cpp @@ -0,0 +1,41 @@ +#include "asset_stream.hpp" + +//Note, based in part on tchow Rainbow's "GameData.cpp" + +#if __ANDROID__ +#include +#include + +#include +extern ANativeActivity *activity; +#else +#include +#endif + +std::unique_ptr< std::istream > asset_stream(std::string const &filename) { +#ifdef __ANDROID__ + assert(activity); //DEBUG + + AAsset *asset = AAssetManager_open(activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); + + if (asset == NULL) { + throw std::runtime_error("Can't open asset '" + filename + "'."); + } + + off64_t size = AAsset_getLength64(asset); + void const *buffer = AAsset_getBuffer(asset); + + if (buffer == NULL) { + throw std::runtime_error("Failed to get pointer to entire contents of asset '" + filename + "'."); + } + + //copy buffer into a string wrapped in a string stream: + std::unique_ptr< std::istringstream > ret(new std::istringstream(std::string(reinterpret_cast< const char * >(buffer), size))); + + AAsset_close(asset); + + return ret; +#else //__ANDROID__ + return std::make_unique< std::ifstream >(filename, std::ios::binary); +#endif +} diff --git a/asset_stream.hpp b/asset_stream.hpp new file mode 100644 index 0000000..8f4d4bf --- /dev/null +++ b/asset_stream.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +//work-around for asset loading on android where assets don't actually have filenames! + +std::unique_ptr< std::istream > asset_stream(std::string const &filename); diff --git a/data_path.cpp b/data_path.cpp index 5da23dd..75c23e3 100644 --- a/data_path.cpp +++ b/data_path.cpp @@ -18,6 +18,7 @@ #endif //WINDOWS +#ifndef __ANDROID__ //This function gets the path to the current executable in various os-specific ways: static std::string get_exe_path() { #if defined(_WIN32) @@ -56,10 +57,16 @@ static std::string get_exe_path() { #error "No idea what the OS is." #endif } +#endif std::string data_path(std::string const &suffix) { + #ifdef __ANDROID__ + std::cout << "Reading from " << suffix << std::endl; //DEBUG + return suffix; //since assets are handled by asset manager and don't have filenames per se + #else static std::string path = get_exe_path(); //cache result of get_exe_path() return path + "/" + suffix; + #endif } /* From Rktcr; to be used eventually! diff --git a/gl_compile_program.cpp b/gl_compile_program.cpp index 165cf82..3df80ca 100644 --- a/gl_compile_program.cpp +++ b/gl_compile_program.cpp @@ -5,7 +5,7 @@ #include #include -static GLuint gl_compile_shader(GLenum type, std::string const &source) { +static GLuint gl_compile_shader(GLenum type, std::string const &source, std::string const &DEBUG_name) { GLuint shader = glCreateShader(type); GLchar const *str = source.c_str(); GLint str_length = GLint(source.size()); @@ -14,7 +14,7 @@ static GLuint gl_compile_shader(GLenum type, std::string const &source) { GLint compile_status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); if (compile_status != GL_TRUE) { - std::cerr << "Failed to compile shader." << std::endl; + std::cerr << "Failed to compile shader [" << DEBUG_name << "]." << std::endl; GLint info_log_length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length); std::vector< GLchar > info_log(info_log_length, 0); @@ -22,18 +22,19 @@ static GLuint gl_compile_shader(GLenum type, std::string const &source) { glGetShaderInfoLog(shader, GLint(info_log.size()), &length, &info_log[0]); std::cerr << "Info log: " << std::string(info_log.begin(), info_log.begin() + length); glDeleteShader(shader); - throw std::runtime_error("Failed to compile shader."); + throw std::runtime_error("Failed to compile shader [" + DEBUG_name + "]."); } return shader; } GLuint gl_compile_program( std::string const &vertex_shader_source, - std::string const &fragment_shader_source + std::string const &fragment_shader_source, + std::string const &DEBUG_program_name ) { - GLuint vertex_shader = gl_compile_shader(GL_VERTEX_SHADER, vertex_shader_source); - GLuint fragment_shader = gl_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source); + GLuint vertex_shader = gl_compile_shader(GL_VERTEX_SHADER, vertex_shader_source, DEBUG_program_name + ".vertex"); + GLuint fragment_shader = gl_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source, DEBUG_program_name + ".fragment"); GLuint program = glCreateProgram(); glAttachShader(program, vertex_shader); diff --git a/gl_compile_program.hpp b/gl_compile_program.hpp index 5670317..acdefcb 100644 --- a/gl_compile_program.hpp +++ b/gl_compile_program.hpp @@ -8,4 +8,6 @@ // throws on compilation error. GLuint gl_compile_program( std::string const &vertex_shader_source, - std::string const &fragment_shader_source); + std::string const &fragment_shader_source, + std::string const &DEBUG_program_name //program name, used for debug messages +); diff --git a/main.cpp b/main.cpp index 9478f64..c3d91cb 100644 --- a/main.cpp +++ b/main.cpp @@ -74,10 +74,48 @@ int start_logger() { return 0; } +bool resumed = false; + +//Based on tchow Rainbow's android/main.cpp +// + modifications inspired by OVR OpenXR SDK's XrApp.cpp +static void handle_cmd(android_app *app, int32_t cmd) { + if (cmd == APP_CMD_INIT_WINDOW) { + std::cout << "APP_CMD_INIT_WINDOW" << std::endl; + //ignore + } else if (cmd == APP_CMD_TERM_WINDOW) { + std::cout << "APP_CMD_TERM_WINDOW" << std::endl; + //ignore + } else if (cmd == APP_CMD_START) { + std::cout << "APP_CMD_START" << std::endl; + //ignore + } else if (cmd == APP_CMD_PAUSE) { + std::cout << "APP_CMD_PAUSE" << std::endl; + resumed = false; + } else if (cmd == APP_CMD_STOP) { + std::cout << "APP_CMD_STOP" << std::endl; + //ignore + } else if (cmd == APP_CMD_RESUME) { + std::cout << "APP_CMD_RESUME" << std::endl; + resumed = true; + } else if (cmd == APP_CMD_DESTROY) { + std::cout << "APP_CMD_DESTROY" << std::endl; + //ignore + } else { + //there are others! android has a lot of states :-/ + //ignore + } +} + +//used by asset_stream(): +ANativeActivity *activity = nullptr; //modeled on OpenXR's "hello_xr" example's main.cpp: // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/main/src/tests/hello_xr/main.cpp void android_main(struct android_app* app) { + activity = app->activity; + + app->onAppCmd = handle_cmd; + app->destroyRequested = 0; if (start_logger() != 0) { __android_log_write(ANDROID_LOG_FATAL, tag, "Failed to start log thread!"); @@ -234,9 +272,11 @@ void android_main(struct android_app* app) { } } - //-------------------- //At this point, OpenGL ES should be good to go! + //-------------------- + // OpenXR setup (using XR helper struct -- see XR.*pp) + XR::PlatformInfo platform; platform.application_vm = app->activity->vm; platform.application_activity = app->activity->clazz; @@ -244,17 +284,90 @@ void android_main(struct android_app* app) { platform.egl_config = config; platform.egl_context = context; - std::unique_ptr< XR > xr(new XR(platform, "gp23 OpenXR example", 1)); + xr = new XR(platform, "gp23 OpenXR example", 1); + + // At this point OpenXR stuff should be ready to use! + + //-------------------- + //load resources + call_load_functions(); + + //------------ create game mode + make current -------------- + Mode::set_current(std::make_shared< PlayMode >()); + //-------------------- + //main loop + + while (!app->destroyRequested && Mode::current) { + //based on https://developer.android.com/ndk/samples/sample_na + // with some modifications from XrApp.cpp from OVR OpenXR SDK + { //read android events: + int ident = 0; + int events = 0; + struct android_poll_source *source = NULL; + + bool animating = resumed || xr->session != XR_NULL_HANDLE || app->destroyRequested != 0; + while ((ident = ALooper_pollAll((animating ? 0 : -1), NULL, &events, (void **)&source)) >= 0) { + if (source != NULL) { + source->process(app, source); + } - //.... + if (app->destroyRequested != 0) { + break; + } + } + } + + //read XR events: + bool was_running = xr->running; + xr->poll_events(); + if (xr->running != was_running) { + if (xr->running) { + std::cerr << "XR is running!" << std::endl; + } else { + std::cerr << "XR is stopped." << std::endl; + } + } + + if (xr->running) { + xr->wait_frame(); //wait for the next frame that needs to be rendered + xr->begin_frame(); //indicate that rendering has started on this frame (NOTE: could *probably* do this later to lower latency if head position isn't important for update) + } + + //compute elapsed time for update: + float elapsed; + if (xr->running) { + static auto previous_time = xr->next_frame.display_time; + auto current_time = xr->next_frame.display_time; + elapsed = (current_time - previous_time) * 1.0e-9; //nanoseconds -> seconds + previous_time = current_time; + } else { + //let's just make time not pass when xr isn't running: + elapsed = 0.0f; + } + + //get current mode to update: + Mode::current->update(std::min(0.1f, elapsed)); + if (!Mode::current) break; + + //(note: playmode has extra logic in here to deal with xr's views array) + Mode::current->draw(glm::uvec2(10,10)); //passing dummy drawable_size; ignored because it will just render to swapchain images instead + + if (xr->running) { + xr->end_frame(); + } + + } //end of main loop //----- teardown ----- //OpenXR connection: - xr.reset(); + if (xr) { + delete xr; + xr = nullptr; + } //EGL stuff: @@ -284,6 +397,7 @@ void android_main(struct android_app* app) { std::cerr << "some other error" << std::endl; } + activity = nullptr; } #else //__ANDROID__