LukasBanana / LLGL

Low Level Graphics Library (LLGL) is a thin abstraction layer for the modern graphics APIs OpenGL, Direct3D, Vulkan, and Metal
BSD 3-Clause "New" or "Revised" License
2.05k stars 139 forks source link

How to run the examples in Android? Give me some suggestions? #107

Closed jayzhen521 closed 6 months ago

jayzhen521 commented 7 months ago

Can you give me some tips about how to run the examples in Android? I want to deploy the LLGL to android but didn't find any relevant content.

I also have two questions:

  1. Which way to create a rendering environment is more suitable when using LLGL for rendering in Android? Use NativeActivity and then create the environment by setting EGL or use GLSurfaceView directly and then implement rendering through a custom Renderer?
  2. Can the native layer switching between OpenGL and Vulkan smoothly?
LukasBanana commented 7 months ago

Please note that the Android port is still in an experimental state. I just got the build script to successfully compile LLGL GLES3 for Android, but I actually haven't set up any deployment targets yet, i.e. no build script generates the APK package yet. Since the example projects include the android_main function, I presume a standard setup with the NativeActivity should be sufficient, i.e. one that doesn't require any Java code. For now I would have to refer you to the official Android NDK documentation and suggest you first manually link the *.so files from the Android example builds to a NativeActivity Android project.

jayzhen521 commented 7 months ago

The upper layer needs to make complex UI layouts and have others use java/kotlin for development, NativeActivity seems not fit for this.

LukasBanana commented 7 months ago

I made some updates to the Android port and got a first example up and running in the Android emulator:

Example.Android.png

I used a modified version of the native-activity example from the android ndk-samples project and build LLGL as static lib for all known ABI versions via ./BuildAndroid.sh -s -d --abi=all.

The modified CMakeLists.txt from the native-activity sample contains this:

#set(LLGL_ROOT ...)
set(LLGL_INCLUDE_DIR "${LLGL_ROOT}/include")
set(LLGL_LIB_DIR "${LLGL_ROOT}/build_android/${ANDROID_ABI}/build")

add_library(lib_llgl STATIC IMPORTED)
set_target_properties(lib_llgl PROPERTIES IMPORTED_LOCATION "${LLGL_LIB_DIR}/libLLGLD.a")

add_library(lib_llgl_gles3 STATIC IMPORTED)
set_target_properties(lib_llgl_gles3 PROPERTIES IMPORTED_LOCATION "${LLGL_LIB_DIR}/libLLGL_OpenGLES3D.a")

target_include_directories(native-activity PRIVATE
    ${ANDROID_NDK}/sources/android/native_app_glue
    ${LLGL_INCLUDE_DIR})

target_link_libraries(native-activity
    android log native_app_glue EGL lib_llgl lib_llgl_gles3 GLESv3)

${ANDROID_ABI} selects the build for the respective ABI, since Android Studio builds for multiple ABI versions at once, at least with the default settings in the ndk-samples project.

If you can't rely on the native-activity example and you have to provide your own EGL context. You would have to add something like this to your LLGL loading code:

#include <LLGL/Backend/OpenGL/NativeHande.h>
...
// Forward custom ::EGLContext object to LLGL
LLGL::OpenGL::RenderSystemNativeHandle nativeRendererHandle = {};
nativeRendererHandle.context = myEGLContext;

rendererDesc.nativeHandle       = &nativeRendererHandle;
rendererDesc.nativeHandleSize   = sizeof(nativeRendererHandle);

I suggest to reproduce the approach of hooking up LLGL to the native-activity sample and go from there. Just so you have something working and then you can modify it to use a different GL view.

UPDATE: Forgot to point out that I'm linking against libLLGLD.a and libLLGL_OpenGLES3D.a since I built with -d option to have debug symbols. Running in Android Studio with lldb provides a pretty decent debugging experience, i.e. the C++ integration works pretty good.

jayzhen521 commented 7 months ago

Thank you so much! I very much agree with your point of view, and I am getting familiar with the relevant code. I will try manual egl first.

I suggest to reproduce the approach of hooking up LLGL to the native-activity sample and go from there. Just so you have something working and then you can modify it to use a different GL view.

This is a good idea!

UPDATE: After a little modification, The native-activity does display correctly.

LukasBanana commented 7 months ago

Would you mind sharing your modifications? And please keep me posted if you get LLGL hooked up to a separate GLView.

jayzhen521 commented 7 months ago

Would you mind sharing your modifications?

I've just implemented it with the help of your instructions, making only a few modifications for drawing triangle. If you need, I can paste the modifications here or create a demo.

I was wondering if I could take a bit of your time to help debug my project. Under the second commit at https://github.com/jayzhen521/SurfaceViewManual, I've constructed my own EGL environment at the native level and managed to draw a Triangle. However, after introducing LLGL and making some adjustments to it, it still doesn't display correctly. It might be due to my insufficient understanding of some EGL principles. The modifications I've made are available at https://github.com/jayzhen521/LLGL.git. I would greatly appreciate your help in reviewing my changes.

(In the SurfaceViewManual, I have just left "Clear" command, but it cannot work)

The ultimate goal of the project I'm building is to leverage the cross-platform capabilities of LLGL. This will enable higher-level developers to work on non-rendering related content, such as UI, using Java, Kotlin, Swift, and other languages.

LukasBanana commented 7 months ago

It looks like you are trying to do the event handling yourself, so when you only want LLGL to draw in drawFrame(), I wouldn't invoke exampleTexturing->Run() and only forward that call via exampleTexturing->DrawFrame(). Run() will enter the main loop and in your case it seems your app only wants to draw a single frame, so this would hang your application.

Your OnDrawFrame implementation is missing one important step. Calling Clear outside a render pass in LLGL is undefined behavior:

commands->Begin();
commands->Clear(LLGL::ClearFlags::ColorDepth, backgroundColor);
commands->End();

The documentation says Clears the specified group of attachments of the active render target, but outside a render pass there are no active render targets. You have to call it inside a render pass like so:

commands->Begin();
commands->BeginRenderPass(*swapChain);
commands->Clear(LLGL::ClearFlags::ColorDepth, backgroundColor);
commands->EndRenderPass();
commands->End();

Having said that, this might not fix your problem, because GLES will probably be just fine calling Clear directly, it simply doesn't follow the design pattern of LLGL and the debug layer should report an error (which is not enabled in your case). But let me know if that changes anything before we dig deeper.

jayzhen521 commented 7 months ago

It looks like you are trying to do the event handling yourself, so when you only want LLGL to draw in drawFrame(), I wouldn't invoke exampleTexturing->Run() and only forward that call via exampleTexturing->DrawFrame(). Run() will enter the main loop and in your case it seems your app only wants to draw a single frame, so this would hang your application.

It works. Screenshot_2024-03-14-14-30-11-095_com am surfaceview_manual

I will add rendering logic for testing later, which will be reflected at https://github.com/jayzhen521/SurfaceViewManual. If LLGL has some bugs related to Android, I will file a PR.

LukasBanana commented 6 months ago

I think we can close this since you have a setup to build LLGL for Android now. Feel free to open this again or a new ticket if need you something else regarding that platform.