diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index b257e5551..3f589c1d7 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -6,6 +6,77 @@ if(APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endif() +# Configure some stuff that needs to be set really early +if(BUILD_ANDROID) + if(NOT DEFINED ENV{JAVA_HOME}) + message(FATAL_ERROR "JAVA_HOME environment variable must be defined for Android build") + endif() + message(STATUS "Using JAVA_HOME = $ENV{JAVA_HOME}") + + execute_process(COMMAND $ENV{JAVA_HOME}/bin/java -version + RESULT_VARIABLE _result + OUTPUT_VARIABLE _output + ERROR_VARIABLE _output + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + + if(NOT _result AND _output MATCHES "version \"([0-9]+).([0-9]+)") + message(STATUS "Java in JAVA_HOME is ${CMAKE_MATCH_1}.${CMAKE_MATCH_2}") + else() + message(STATUS "Java in JAVA_HOME is unknown version ${_output} ${_result}") + endif() + + if(DEFINED ENV{ANDROID_HOME} AND EXISTS "$ENV{ANDROID_HOME}/build-tools") + set(ANDROID_SDK_ROOT_PATH "$ENV{ANDROID_HOME}") + elseif(DEFINED ENV{ANDROID_SDK_ROOT} AND EXISTS "$ENV{ANDROID_SDK_ROOT}/build-tools") + set(ANDROID_SDK_ROOT_PATH "$ENV{ANDROID_SDK_ROOT}") + elseif(DEFINED ENV{ANDROID_SDK} AND EXISTS "$ENV{ANDROID_SDK}/build-tools") + set(ANDROID_SDK_ROOT_PATH "$ENV{ANDROID_SDK}") + else() + message(FATAL_ERROR "Can't locate Android SDK, set ANDROID_HOME, ANDROID_SDK_ROOT or ANDROID_SDK") + endif() + + message(STATUS "Using Android SDK found in ${ANDROID_SDK_ROOT_PATH}") + + if(DEFINED ENV{ANDROID_NDK_HOME} AND EXISTS "$ENV{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake") + set(ANDROID_NDK_ROOT_PATH "$ENV{ANDROID_NDK_HOME}") + elseif(DEFINED ENV{ANDROID_NDK_ROOT} AND EXISTS "$ENV{ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake") + set(ANDROID_NDK_ROOT_PATH "$ENV{ANDROID_NDK_ROOT}") + elseif(DEFINED ENV{NDK_HOME} AND EXISTS "$ENV{NDK_HOME}/build/cmake/android.toolchain.cmake") + set(ANDROID_NDK_ROOT_PATH "$ENV{NDK_HOME}") + elseif(DEFINED ENV{ANDROID_NDK} AND EXISTS "$ENV{ANDROID_NDK}/build/cmake/android.toolchain.cmake") + set(ANDROID_NDK_ROOT_PATH "$ENV{ANDROID_NDK}") + else() + message(FATAL_ERROR "Can't locate Android NDK, set ANDROID_NDK_HOME, ANDROID_NDK_ROOT, NDK_HOME or ANDROID_NDK") + endif() + + message(STATUS "Using Android NDK found in ${ANDROID_NDK_ROOT_PATH}") + + set(CMAKE_TOOLCHAIN_FILE + "${ANDROID_NDK_ROOT_PATH}/build/cmake/android.toolchain.cmake" + CACHE STRING + "The Android toolchain file") + + # Set default API level to 21 if not configured explicitly + if(NOT ANDROID_PLATFORM) + set(ANDROID_PLATFORM "android-21") + endif() + + # default to libc++_static as the other options can cause crashes + if(NOT ANDROID_STL) + set(ANDROID_STL "c++_static") + endif() + + # Choose clang if the NDK has both gcc and clang, since gcc sometimes fails + set(CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION "clang") + + # Default to arm64 if nothing is specified on the command line. + # Options are {armeabi-v7a,arm64-v8a} + set(ANDROID_ABI "arm64-v8a" CACHE STRING "The Android ABI to build for") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID=1") +endif() + set(PROJECT demos) set(VULKAN_SRC @@ -131,7 +202,7 @@ project(demos) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - list(APPEND warning_flags -Werror) + list(APPEND warning_flags -Werror -Wno-unused-result) string(REPLACE ";" " " warning_flags "${warning_flags}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${warning_flags}") endif() @@ -148,6 +219,20 @@ if(APPLE) apple/apple_window.h) add_executable(demos ${SRC} ${VULKAN_SRC}) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") +elseif(BUILD_ANDROID) + string(REPLACE "\\" "/" GLUE_SOURCE "${ANDROID_NDK_ROOT_PATH}/sources/android/native_app_glue/android_native_app_glue.c") + include_directories(${ANDROID_NDK_ROOT_PATH}/sources/android/native_app_glue) + + list(APPEND OPENGL_SRC + 3rdparty/glad/glad_egl.c + gl/gl_test_android.cpp) + list(APPEND SRC + android/android_platform.cpp + android/android_platform.h + android/android_window.cpp + android/android_window.h + ${GLUE_SOURCE}) + add_library(demos SHARED ${SRC} ${VULKAN_SRC} ${OPENGL_SRC}) elseif(UNIX) list(APPEND OPENGL_SRC 3rdparty/glad/glad_glx.c @@ -162,12 +247,20 @@ endif() install(TARGETS demos DESTINATION .) -if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(SHADERC_DIR "linux64") - set(BIN_SUFFIX "x64") +if(BUILD_ANDROID) + set(SHADERC_DIR "dummy") + set(BIN_SUFFIX "") + target_link_libraries(demos PRIVATE ${ANDROID_NDK_ROOT_PATH}/sources/third_party/shaderc/obj/local/${ANDROID_ABI}/libshaderc_combined.a) + target_compile_definitions(demos PRIVATE -DHAVE_SHADERC=1) + target_include_directories(demos PRIVATE ${ANDROID_NDK_ROOT_PATH}/sources/third_party/shaderc/include) else() - set(SHADERC_DIR "linux32") - set(BIN_SUFFIX "x86") + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(SHADERC_DIR "linux64") + set(BIN_SUFFIX "_x64") + else() + set(SHADERC_DIR "linux32") + set(BIN_SUFFIX "_x86") + endif() endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/shaderc/${SHADERC_DIR}) @@ -192,11 +285,156 @@ if(APPLE) target_compile_definitions(demos PRIVATE -DVK_USE_PLATFORM_MACOS_MVK=1) set(LIBS ${COCOA_LIBRARY} ${QUARTZCORE_LIBRARY} ${METAL_LIBRARY}) +elseif(BUILD_ANDROID) + target_compile_definitions(demos PRIVATE -DVK_USE_PLATFORM_ANDROID_KHR=1) + list(APPEND LIBS PRIVATE -llog -landroid -lEGL) elseif(UNIX) target_compile_definitions(demos PRIVATE -DVK_USE_PLATFORM_XCB_KHR=1) set(LIBS -lX11 -lxcb -lX11-xcb) endif() target_link_libraries(demos PRIVATE ${LIBS} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) -set_target_properties(demos PROPERTIES OUTPUT_NAME demos_${BIN_SUFFIX}) +set_target_properties(demos PROPERTIES OUTPUT_NAME demos${BIN_SUFFIX}) +if(ANDROID) + # Android sets this to off becuase Android is always terrible forever. + # It breaks finding java in the path, so enable it again + set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH ON) + + ############################# + # We need to check that 'java' in PATH is new enough. Temporarily unset the JAVA_HOME env, + # then invoke FindJava.cmake which will search just the PATH, then re-set it. + set(SAVE_JAVA_HOME $ENV{JAVA_HOME}) + + set(ENV{JAVA_HOME} "") + find_package(Java) + set(ENV{JAVA_HOME} ${SAVE_JAVA_HOME}) + + if(NOT ${Java_FOUND}) + message(FATAL_ERROR "Building Android requires the 'java' program in your PATH. It must be at least Java 8 (1.8)") + endif() + + if(${Java_VERSION} VERSION_LESS 1.8) + message(FATAL_ERROR "Building Android requires the 'java' program in your PATH to be at least Java 8 (1.8)") + endif() + message(STATUS "Using Java of version ${Java_VERSION}") + + set(ANDROID_BUILD_TOOLS_VERSION "" CACHE STRING "Version of Android build-tools to use instead of the default") + if(ANDROID_BUILD_TOOLS_VERSION STREQUAL "") + # Enumerate the build tools versions available, and pick the most recent + file(GLOB __buildTools RELATIVE "${ANDROID_SDK_ROOT_PATH}/build-tools" "${ANDROID_SDK_ROOT_PATH}/build-tools/*") + list(SORT __buildTools) + + list(GET __buildTools -1 ANDROID_BUILD_TOOLS_VERSION) + + unset(__buildTools) + endif() + message(STATUS "Using Android build-tools version ${ANDROID_BUILD_TOOLS_VERSION}") + + set(APK_TARGET_ID "" CACHE STRING "The Target ID to build the APK for like 'android-99', use to choose another one.") + if(APK_TARGET_ID STREQUAL "") + # This seems different from the platform we're targetting, + # default to the latest available that's greater or equal to our target platform + file(GLOB __platforms RELATIVE "${ANDROID_SDK_ROOT_PATH}/platforms" "${ANDROID_SDK_ROOT_PATH}/platforms/*") + list(SORT __platforms) + + # In case we don't find one, target the latest platform + list(GET __platforms -1 APK_TARGET_ID) + + string(REPLACE "android-" "" __targetPlat "${ANDROID_PLATFORM}") + + # We require at least android 23 for Activity.requestPermissions + if(__targetPlat LESS 23) + set(__targetPlat 23) + endif() + + foreach( __plat ${__platforms}) + string(REPLACE "android-" "" __curPlat "${__plat}") + + if(NOT (__curPlat LESS __targetPlat) ) + set(APK_TARGET_ID "android-${__curPlat}") + break() + endif() + endforeach() + + unset(__platforms) + unset(__targetPlat) + unset(__curPlat) + endif() + message(STATUS "Using android.jar from platform ${APK_TARGET_ID}") + + # Suffix for scripts rather than binaries, which is needed explicitly on windows + set(TOOL_SCRIPT_EXTENSION "") + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(TOOL_SCRIPT_EXTENSION ".bat") + endif() + + set(BUILD_TOOLS "${ANDROID_SDK_ROOT_PATH}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}") + set(RT_JAR "$ENV{JAVA_HOME}/jre/lib/rt.jar") + set(JAVA_BIN "$ENV{JAVA_HOME}/bin") + + string(REPLACE "\\" "/" ANDROID_JAR "${ANDROID_SDK_ROOT_PATH}/platforms/${APK_TARGET_ID}/android.jar") + if(CMAKE_HOST_WIN32) + set(CLASS_PATH "${ANDROID_JAR}\;obj") + else() + set(CLASS_PATH "${ANDROID_JAR}:obj") + endif() + set(KEYSTORE ${CMAKE_CURRENT_BINARY_DIR}/debug.keystore) + add_custom_command(OUTPUT ${KEYSTORE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${JAVA_BIN}/keytool -genkey -keystore ${KEYSTORE} -storepass android -alias rdocandroidkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=, OU=, O=, L=, S=, C=") + + set(APK_VERSION_CODE "10") + set(APK_VERSION_NAME "demos_build") + + # Set the package name based on the ABI + if(ANDROID_ABI STREQUAL "armeabi-v7a") + set(ABI_EXTENSION_NAME "arm32") + elseif(ANDROID_ABI STREQUAL "arm64-v8a") + set(ABI_EXTENSION_NAME "arm64") + elseif(ANDROID_ABI STREQUAL "x86") + set(ABI_EXTENSION_NAME "x86") + elseif(ANDROID_ABI STREQUAL "x86_64") + set(ABI_EXTENSION_NAME "x64") + else() + message(FATAL_ERROR "ABI ${ANDROID_ABI} is not supported.") + endif() + + set(RENDERDOC_ANDROID_PACKAGE_NAME "org.renderdoc.demos.${ABI_EXTENSION_NAME}") + + set(APK_FILE ${CMAKE_BINARY_DIR}/bin/${RENDERDOC_ANDROID_PACKAGE_NAME}.apk) + add_custom_target(apk ALL + DEPENDS ${APK_FILE}) + + # Copy in android package files, replacing the package name with the architecture-specific package name + configure_file(android/Loader.java ${CMAKE_CURRENT_BINARY_DIR}/src/org/renderdoc/demos/Loader.java) + configure_file(android/AndroidManifest.xml ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml) + configure_file(android/icon.png ${CMAKE_CURRENT_BINARY_DIR}/res/drawable/icon.png COPYONLY) + + add_custom_command(OUTPUT ${APK_FILE} + DEPENDS demos + DEPENDS ${KEYSTORE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory libs/lib/${ANDROID_ABI} + COMMAND ${CMAKE_COMMAND} -E make_directory obj + COMMAND ${CMAKE_COMMAND} -E make_directory bin + COMMAND ${CMAKE_COMMAND} -E copy $ libs/lib/${ANDROID_ABI}/$ + ) + + set(D8_SCRIPT "${BUILD_TOOLS}/d8${TOOL_SCRIPT_EXTENSION}") + if(NOT EXISTS ${D8_SCRIPT}) + set(DEX_COMMAND ${BUILD_TOOLS}/dx${TOOL_SCRIPT_EXTENSION} --dex --output=bin/classes.dex ./obj) + else() + set(DEX_COMMAND ${D8_SCRIPT} --output ./bin/ ./obj/org/renderdoc/demos/${ABI_EXTENSION_NAME}/*.class) + endif() + + add_custom_command(OUTPUT ${APK_FILE} APPEND + COMMAND ${BUILD_TOOLS}/aapt package -f -m -S res -J src -M AndroidManifest.xml -I ${ANDROID_JAR} + COMMAND ${JAVA_BIN}/javac -d ./obj -source 1.7 -target 1.7 -bootclasspath ${RT_JAR} -classpath "${CLASS_PATH}" -sourcepath src src/org/renderdoc/demos/*.java + COMMAND ${DEX_COMMAND} + COMMAND ${BUILD_TOOLS}/aapt package -f -M AndroidManifest.xml --version-code ${APK_VERSION_CODE} --version-name ${APK_VERSION_NAME} -S res -I ${ANDROID_JAR} -F demos-unaligned.apk bin libs + COMMAND ${BUILD_TOOLS}/zipalign -f 4 demos-unaligned.apk demos.apk + COMMAND ${BUILD_TOOLS}/apksigner${TOOL_SCRIPT_EXTENSION} sign --ks ${KEYSTORE} --ks-pass pass:android --key-pass pass:android --ks-key-alias rdocandroidkey demos.apk + COMMAND ${CMAKE_COMMAND} -E copy demos.apk ${APK_FILE}) + +endif() diff --git a/util/test/demos/android/AndroidManifest.xml b/util/test/demos/android/AndroidManifest.xml new file mode 100644 index 000000000..e60885d6f --- /dev/null +++ b/util/test/demos/android/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/util/test/demos/android/Loader.java b/util/test/demos/android/Loader.java new file mode 100644 index 000000000..419fc73ec --- /dev/null +++ b/util/test/demos/android/Loader.java @@ -0,0 +1,20 @@ +package @RENDERDOC_ANDROID_PACKAGE_NAME@; +import android.os.Build; +import android.app.Activity; +import android.view.WindowManager; +import android.os.Environment; +import android.content.Intent; + +public class Loader extends android.app.NativeActivity +{ + /* load our native library */ + static { + System.loadLibrary("demos"); + } + + @Override + protected void onCreate(android.os.Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } +} diff --git a/util/test/demos/android/android_platform.cpp b/util/test/demos/android/android_platform.cpp new file mode 100644 index 000000000..17c07ac3b --- /dev/null +++ b/util/test/demos/android/android_platform.cpp @@ -0,0 +1,78 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2023 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include +#include +#include "test_common.h" + +uint64_t GetMemoryUsage() +{ + FILE *f = fopen("/proc/self/statm", "r"); + + if(f == NULL) + { + TEST_WARN("Couldn't open /proc/self/statm"); + return 0; + } + + char line[512] = {}; + fgets(line, 511, f); + + fclose(f); + + uint32_t rssPages = 0; + int num = sscanf(line, "%*u %u", &rssPages); + + if(num == 1 && rssPages > 0) + return rssPages * (uint64_t)sysconf(_SC_PAGESIZE); + + return 0; +} + +std::string GetCWD() +{ + char cwd[MAX_PATH + 1] = {0}; + getcwd(cwd, MAX_PATH); + + std::string cwdstr = cwd; + + for(size_t i = 0; i < cwdstr.size(); i++) + if(cwdstr[i] == '\\') + cwdstr[i] = '/'; + + while(cwdstr.back() == '/' || cwdstr.back() == '\\') + cwdstr.pop_back(); + + return cwdstr; +} + +std::string GetEnvVar(const char *var) +{ + return ""; +} + +std::string GetExecutableName() +{ + return "__undefined__"; +} diff --git a/util/test/demos/android/android_platform.h b/util/test/demos/android/android_platform.h new file mode 100644 index 000000000..36e830945 --- /dev/null +++ b/util/test/demos/android/android_platform.h @@ -0,0 +1,44 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2023 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#define DEBUG_BREAK() raise(SIGTRAP) + +#define MAX_PATH 256 + +#define msleep(time) usleep((time)*1000) + +#define EXECUTABLE_SUFFIX "" + +#define MakeDir(dir) mkdir(dir, 0755) + +std::string GetExecutableName(); diff --git a/util/test/demos/android/android_window.cpp b/util/test/demos/android/android_window.cpp new file mode 100644 index 000000000..cc5848dfb --- /dev/null +++ b/util/test/demos/android/android_window.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2023 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include "android_window.h" + +#include + +extern struct android_app *android_state; + +AndroidWindow::AndroidWindow(int width, int height, const char *title) : GraphicsWindow(title) +{ + window = android_state->window; + TEST_LOG("android window %p", window); +} + +AndroidWindow::~AndroidWindow() +{ +} + +void AndroidWindow::Resize(int width, int height) +{ +} + +bool AndroidWindow::Update() +{ + return true; +} diff --git a/util/test/demos/android/android_window.h b/util/test/demos/android/android_window.h new file mode 100644 index 000000000..9f45fbd5b --- /dev/null +++ b/util/test/demos/android/android_window.h @@ -0,0 +1,41 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2023 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#pragma once + +#include "../test_common.h" + +struct ANativeWindow; +typedef void *EGLSurface; + +struct AndroidWindow : public GraphicsWindow +{ + AndroidWindow(int width, int height, const char *title); + ~AndroidWindow(); + void Resize(int width, int height); + bool Update(); + + ANativeWindow *window; + EGLSurface surface; +}; diff --git a/util/test/demos/android/icon.png b/util/test/demos/android/icon.png new file mode 100644 index 000000000..c47395bc9 Binary files /dev/null and b/util/test/demos/android/icon.png differ diff --git a/util/test/demos/android/wrap.sh b/util/test/demos/android/wrap.sh new file mode 100644 index 000000000..958d6130f --- /dev/null +++ b/util/test/demos/android/wrap.sh @@ -0,0 +1,8 @@ +#!/system/bin/sh +export renderdoc__replay__marker=1 +# chain to asan's wrap if needed, now that we exported the env var +if [ -f asan.sh ]; then + ./asan.sh "$@" +else + exec "$@" +fi diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index b4dd874ad..badcb279b 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -121,6 +121,12 @@ + + true + + + true + @@ -267,6 +273,9 @@ true + + true + @@ -358,6 +367,12 @@ + + true + + + true + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 8bb026568..3d7bbf1c6 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -649,6 +649,15 @@ D3D12\demos + + Android + + + Android + + + OpenGL + @@ -720,6 +729,9 @@ {25327220-a428-4ea2-8894-300df4a61a33} + + {cd8bc334-25e7-40a0-99fb-fff5ccf33687} + @@ -913,5 +925,11 @@ 3rdparty\md5 + + Android + + + Android + \ No newline at end of file diff --git a/util/test/demos/gl/gl_test.cpp b/util/test/demos/gl/gl_test.cpp index b3c530433..d648a9ab1 100644 --- a/util/test/demos/gl/gl_test.cpp +++ b/util/test/demos/gl/gl_test.cpp @@ -25,9 +25,19 @@ #include "gl_test.h" #include -static std::string common = R"EOSHADER( +#if defined(ANDROID) +static std::string version = "#version 320 es"; +#else +static std::string version = "#version 410 core"; +#endif -#version 410 core +static std::string common = version + R"EOSHADER( + + +#if defined(GL_ES) +precision highp float; +precision highp int; +#endif #define v2f v2f_block \ { \ @@ -60,7 +70,7 @@ std::string GLDefaultPixel = common + R"EOSHADER( in v2f vertIn; -layout(location = 0, index = 0) out vec4 Color; +layout(location = 0) out vec4 Color; void main() { diff --git a/util/test/demos/gl/gl_test_android.cpp b/util/test/demos/gl/gl_test_android.cpp new file mode 100644 index 000000000..fdeba9426 --- /dev/null +++ b/util/test/demos/gl/gl_test_android.cpp @@ -0,0 +1,172 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2023 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include "gl_test.h" +#include +#include +#include "3rdparty/glad/glad_egl.h" + +#include "../android/android_window.h" + +EGLDisplay eglDisplay; +EGLConfig config; + +void OpenGLGraphicsTest::Prepare(int argc, char **argv) +{ + GraphicsTest::Prepare(argc, argv); + + static bool prepared = false; + static void *libEGL = NULL; + + if(!prepared) + { + prepared = true; + + libEGL = dlopen("libEGL.so", RTLD_GLOBAL | RTLD_NOW); + } + + if(!libEGL) + Avail = "libEGL.so is not available"; +} + +bool OpenGLGraphicsTest::Init() +{ + if(!GraphicsTest::Init()) + return false; + + gladLoadEGL(); + + eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + TEST_LOG("android display %p %d", eglDisplay, eglGetError()); + + int major = 0, minor = 0; + EGLBoolean initialised = eglInitialize(eglDisplay, &major, &minor); + TEST_LOG("android init %d %d => %d", major, minor, initialised); + + const EGLint configAttribs[] = {EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_COLOR_BUFFER_TYPE, + EGL_RGB_BUFFER, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_NONE}; + + EGLint numConfigs; + eglChooseConfig(eglDisplay, configAttribs, &config, 1, &numConfigs); + + TEST_LOG("android config %p %d (%d)", config, eglGetError(), numConfigs); + + mainWindow = new AndroidWindow(screenWidth, screenHeight, screenTitle); + + mainContext = MakeContext(mainWindow, NULL); + + if(!mainWindow || !mainContext) + { + delete mainWindow; + TEST_ERROR("Couldn't initialise context"); + return false; + } + + ActivateContext(mainWindow, mainContext); + + if(!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) + { + delete mainWindow; + TEST_ERROR("Error initialising glad"); + return false; + } + + PostInit(); + + return true; +} + +GraphicsWindow *OpenGLGraphicsTest::MakeWindow(int width, int height, const char *title) +{ + return new AndroidWindow(width, height, title); +} + +void *OpenGLGraphicsTest::MakeContext(GraphicsWindow *win, void *share) +{ + AndroidWindow *droidwin = (AndroidWindow *)win; + + int attribs[64] = {0}; + int i = 0; + + attribs[i++] = EGL_CONTEXT_CLIENT_VERSION; + attribs[i++] = 3; + attribs[i++] = EGL_CONTEXT_FLAGS_KHR; + if(debugDevice) + attribs[i++] = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; + else + attribs[i++] = 0; + attribs[i++] = EGL_NONE; + attribs[i++] = EGL_NONE; + + EGLContext ctx = eglCreateContext(eglDisplay, config, share, attribs); + + TEST_LOG("android context %p %d", ctx, eglGetError()); + droidwin->surface = eglCreateWindowSurface(eglDisplay, config, droidwin->window, NULL); + TEST_LOG("android surface %p %d", droidwin->surface, eglGetError()); + + return ctx; +} + +void OpenGLGraphicsTest::DestroyContext(void *ctx) +{ + if(ctx == NULL) + return; + + eglMakeCurrent(eglDisplay, 0L, 0L, NULL); + eglDestroyContext(eglDisplay, ctx); +} + +void OpenGLGraphicsTest::ActivateContext(GraphicsWindow *win, void *ctx, bool alt) +{ + AndroidWindow *droidwin = (AndroidWindow *)win; + + if(ctx == NULL) + { + if(win == NULL) + return; + + eglMakeCurrent(eglDisplay, NULL, NULL, NULL); + return; + } + + eglMakeCurrent(eglDisplay, droidwin->surface, droidwin->surface, (EGLContext)ctx); +} + +void OpenGLGraphicsTest::Present(GraphicsWindow *window) +{ + AndroidWindow *droidwin = (AndroidWindow *)window; + + eglSwapBuffers(eglDisplay, droidwin->surface); +} diff --git a/util/test/demos/main.cpp b/util/test/demos/main.cpp index dc7e3a7d7..b0d494d23 100644 --- a/util/test/demos/main.cpp +++ b/util/test/demos/main.cpp @@ -139,6 +139,26 @@ void NuklearShutdown() UnregisterClassW(wc.lpszClassName, wc.hInstance); } +#elif defined(ANDROID) + +nk_context *NuklearInit(int width, int height, const char *title) +{ + return NULL; +} + +bool NuklearTick(nk_context *ctx) +{ + return false; +} + +void NuklearRender() +{ +} + +void NuklearShutdown() +{ +} + #elif defined(__linux__) #define NK_XLIB_IMPLEMENTATION @@ -710,3 +730,109 @@ int WINAPI wWinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInstance, _In_ } #endif + +#if defined(ANDROID) +#include +#include + +#include + +struct android_app *android_state; +pthread_t cmdthread_handle = 0; + +#define ANDROID_LOG(...) __android_log_print(ANDROID_LOG_INFO, "rd_demos", __VA_ARGS__); + +std::vector getArgs() +{ + JNIEnv *env; + android_state->activity->vm->AttachCurrentThread(&env, 0); + + jobject me = android_state->activity->clazz; + + jclass acl = env->GetObjectClass(me); // class pointer of NativeActivity + jmethodID giid = env->GetMethodID(acl, "getIntent", "()Landroid/content/Intent;"); + jobject intent = env->CallObjectMethod(me, giid); // Got our intent + + jclass icl = env->GetObjectClass(intent); // class pointer of Intent + jmethodID gseid = + env->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"); + + jstring jsParam1 = (jstring)env->CallObjectMethod(intent, gseid, env->NewStringUTF("rd_demos")); + + std::vector ret; + if(jsParam1) // Check if arg value found + { + ret.push_back("rd_demos"); + const char *param1 = env->GetStringUTFChars(jsParam1, 0); + std::istringstream iss(param1); + while(iss) + { + std::string sub; + iss >> sub; + ret.push_back(sub); + } + } + android_state->activity->vm->DetachCurrentThread(); + + return ret; +} + +void *cmdthread(void *) +{ + ANDROID_LOG("cmdthread"); + std::vector args = getArgs(); + if(args.size()) + { + std::vector argv; + for(size_t i = 0; i < args.size(); i++) + { + ANDROID_LOG("argv %d: %s", (int)i, args[i].c_str()); + argv.push_back(&args[i][0]); + } + int argc = argv.size(); + argv.push_back(NULL); + ANDROID_LOG("premain"); + main(argc, argv.data()); + ANDROID_LOG("postmain"); + } + + // activity is done and should be closed + ANativeActivity_finish(android_state->activity); + + return NULL; +} + +void handle_cmd(android_app *app, int32_t cmd) +{ + if(cmd == APP_CMD_INIT_WINDOW) + { + ANDROID_LOG("APP_CMD_INIT_WINDOW"); + pthread_create(&cmdthread_handle, NULL, cmdthread, NULL); + } +} + +void android_main(struct android_app *state) +{ + android_state = state; + android_state->onAppCmd = handle_cmd; + + ANDROID_LOG("android_main"); + + // Used to poll the events in the main loop + int events; + android_poll_source *source; + do + { + if(ALooper_pollAll(1, nullptr, &events, (void **)&source) >= 0) + { + if(source != NULL) + source->process(android_state, source); + } + } while(android_state->destroyRequested == 0); + + ANDROID_LOG("end android_main"); + + android_state = NULL; +} + +#endif diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp index bb9fbca52..0441aba17 100644 --- a/util/test/demos/test_common.cpp +++ b/util/test/demos/test_common.cpp @@ -154,6 +154,10 @@ std::string trim(const std::string &str) static char printBuf[4096] = {}; static FILE *logFile = NULL; +#if defined(ANDROID) +#include +#endif + void DebugPrint(const char *fmt, ...) { va_list args; @@ -175,6 +179,10 @@ void DebugPrint(const char *fmt, ...) #if defined(WIN32) OutputDebugStringA(printBuf); #endif + +#if defined(ANDROID) + __android_log_print(ANDROID_LOG_INFO, "rd_demos", "%s", printBuf); +#endif } void LoadXPM(const char **XPM, Texture &tex) @@ -215,6 +223,10 @@ void LoadXPM(const char **XPM, Texture &tex) // having to remove the built shaderc files #define USE_LINKED_SHADERC (1 && HAVE_SHADERC) +#if !USE_LINKED_SHADERC && defined(ANDROID) +#error "can't execute shaderc on android" +#endif + #if USE_LINKED_SHADERC #include #else @@ -569,6 +581,10 @@ bool GraphicsTest::Init() HMODULE mod = GetModuleHandleA("renderdoc.dll"); if(mod) RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); +#elif defined(ANDROID) + void *mod = dlopen("libVkLayer_GLES_RenderDoc.so", RTLD_NOW | RTLD_NOLOAD); + if(mod) + RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI"); #elif defined(__linux__) void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); if(mod) diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h index a2667e63f..0de06eb4e 100644 --- a/util/test/demos/test_common.h +++ b/util/test/demos/test_common.h @@ -26,6 +26,8 @@ #if defined(WIN32) #include "win32/win32_platform.h" +#elif defined(ANDROID) +#include "android/android_platform.h" #elif defined(__linux__) #include "linux/linux_platform.h" #elif defined(__APPLE__) diff --git a/util/test/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp index dc46384c3..7d83e8f60 100644 --- a/util/test/demos/vk/vk_test.cpp +++ b/util/test/demos/vk/vk_test.cpp @@ -102,6 +102,8 @@ void main() #if defined(WIN32) #include "../win32/win32_window.h" +#elif defined(ANDROID) +#include "../android/android_window.h" #elif defined(__linux__) #include "../linux/linux_window.h" #elif defined(__APPLE__) @@ -161,6 +163,8 @@ void VulkanGraphicsTest::Prepare(int argc, char **argv) #if defined(WIN32) enabledInstExts.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif defined(ANDROID) + enabledInstExts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #elif defined(__linux__) enabledInstExts.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); @@ -592,6 +596,11 @@ void VulkanGraphicsTest::Prepare(int argc, char **argv) if(computeQueueFamilyIndex == ~0U) computeQueueFamilyIndex = q; } + else if(queueProps[q].queueFlags & VK_QUEUE_TRANSFER_BIT) + { + if(transferQueueFamilyIndex == ~0U) + transferQueueFamilyIndex = q; + } } // if no queue has been selected, find it now @@ -679,8 +688,13 @@ bool VulkanGraphicsTest::Init() queueCreates.push_back(vkh::DeviceQueueCreateInfo(graphicsQueueFamilyIndex, 1, priorities)); if(queueFamilyIndex != computeQueueFamilyIndex && (graphicsQueueFamilyIndex != computeQueueFamilyIndex || !forceGraphicsQueue) && - forceComputeQueue) + computeQueueFamilyIndex != ~0U && forceComputeQueue) queueCreates.push_back(vkh::DeviceQueueCreateInfo(computeQueueFamilyIndex, 1, priorities)); + if(queueFamilyIndex != transferQueueFamilyIndex && + graphicsQueueFamilyIndex != transferQueueFamilyIndex && + computeQueueFamilyIndex != transferQueueFamilyIndex && transferQueueFamilyIndex != ~0U && + forceTransferQueue) + queueCreates.push_back(vkh::DeviceQueueCreateInfo(transferQueueFamilyIndex, 1, priorities)); CHECK_VKR(vkCreateDevice( phys, vkh::DeviceCreateInfo(queueCreates, enabledLayers, devExts, features).next(devInfoNext), @@ -744,6 +758,8 @@ VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char * { #if defined(WIN32) GraphicsWindow *platWin = new Win32Window(width, height, title); +#elif defined(ANDROID) + GraphicsWindow *platWin = new AndroidWindow(width, height, title); #elif defined(__linux__) GraphicsWindow *platWin = new X11Window(width, height, 0, title); #elif defined(__APPLE__) @@ -1325,6 +1341,15 @@ VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) createInfo.hinstance = GetModuleHandleA(NULL); vkCreateWin32SurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); +#elif defined(ANDROID) + VkAndroidSurfaceCreateInfoKHR createInfo; + + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.window = ((AndroidWindow *)win)->window; + + vkCreateAndroidSurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); #elif defined(__linux__) VkXcbSurfaceCreateInfoKHR createInfo; diff --git a/util/test/demos/vk/vk_test.h b/util/test/demos/vk/vk_test.h index 0245eb185..39202435f 100644 --- a/util/test/demos/vk/vk_test.h +++ b/util/test/demos/vk/vk_test.h @@ -273,8 +273,10 @@ struct VulkanGraphicsTest : public GraphicsTest bool forceGraphicsQueue = false; bool forceComputeQueue = false; + bool forceTransferQueue = false; uint32_t graphicsQueueFamilyIndex = ~0U; uint32_t computeQueueFamilyIndex = ~0U; + uint32_t transferQueueFamilyIndex = ~0U; bool hasExt(const char *ext);