From b8bd99a24a12b986f5f0608586b6b5161323e704 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 1 Jan 2018 12:41:12 +0000 Subject: [PATCH] Add a simple preview placeholder for android * This means that at least when nothing else is happening the remote server isn't completely blank and unresponsive. --- renderdoccmd/android/AndroidManifest.xml | 2 +- renderdoccmd/renderdoccmd_android.cpp | 247 ++++++++++++++++++++++- 2 files changed, 238 insertions(+), 11 deletions(-) diff --git a/renderdoccmd/android/AndroidManifest.xml b/renderdoccmd/android/AndroidManifest.xml index 6844df737..64caaf9b0 100644 --- a/renderdoccmd/android/AndroidManifest.xml +++ b/renderdoccmd/android/AndroidManifest.xml @@ -12,7 +12,7 @@ - + diff --git a/renderdoccmd/renderdoccmd_android.cpp b/renderdoccmd/renderdoccmd_android.cpp index 3ec454e1e..a9893466c 100644 --- a/renderdoccmd/renderdoccmd_android.cpp +++ b/renderdoccmd/renderdoccmd_android.cpp @@ -23,6 +23,9 @@ ******************************************************************************/ #include "renderdoccmd.h" +#include +#include +#include #include #include #include @@ -35,13 +38,20 @@ extern "C" { } #include -#define LOGCAT_TAG "renderdoc" +#define ANDROID_LOG(...) __android_log_print(ANDROID_LOG_INFO, "renderdoc", __VA_ARGS__); using std::string; using std::vector; using std::istringstream; struct android_app *android_state; +pthread_t cmdthread_handle = 0; + +struct +{ + pthread_mutex_t lock; + pthread_mutexattr_t attr; +} m_DrawLock; void Daemonise() { @@ -52,6 +62,8 @@ void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &display { ANativeWindow *connectionScreenWindow = android_state->window; + pthread_mutex_lock(&m_DrawLock.lock); + IReplayOutput *out = renderer->CreateOutput(WindowingSystem::Android, connectionScreenWindow, ReplayOutputType::Texture); @@ -61,11 +73,200 @@ void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &display { renderer->SetFrameEvent(10000000, true); - __android_log_print(ANDROID_LOG_INFO, LOGCAT_TAG, "Frame %i", i); + ANDROID_LOG("Frame %i", i); out->Display(); usleep(100000); } + + pthread_mutex_unlock(&m_DrawLock.lock); +} + +void DisplayGenericSplash() +{ + // if something else is drawing and holding the lock, then bail + if(pthread_mutex_trylock(&m_DrawLock.lock)) + return; + + // since we're not pumping this continually and we only draw when we need to, we can just do the + // full initialisation and teardown every time. This means we don't have to pay attention to + // whether something else needs to create a context on the window - we just do it + // opportunistically when we can hold the draw lock. + // This only takes about 30ms anyway, so it's still technically realtime, right!? + + // fetch everything dynamically. We can't link against libEGL otherwise it messes with the hooking + // in the main library. Just declare local function pointers with the real names + void *libEGL = dlopen("libEGL.so", RTLD_NOW); + +#define DLSYM_GET(name) decltype(&::name) name = (decltype(&::name))dlsym(libEGL, #name); +#define GPA_GET(name) decltype(&::name) name = (decltype(&::name))eglGetProcAddress(#name); + + DLSYM_GET(eglBindAPI); + DLSYM_GET(eglGetDisplay); + DLSYM_GET(eglInitialize); + DLSYM_GET(eglChooseConfig); + DLSYM_GET(eglCreateContext); + DLSYM_GET(eglCreateWindowSurface); + DLSYM_GET(eglMakeCurrent); + DLSYM_GET(eglDestroySurface); + DLSYM_GET(eglDestroyContext); + DLSYM_GET(eglGetProcAddress); + DLSYM_GET(eglSwapBuffers); + + GPA_GET(glCreateShader); + GPA_GET(glShaderSource); + GPA_GET(glCompileShader); + GPA_GET(glCreateProgram); + GPA_GET(glAttachShader); + GPA_GET(glLinkProgram); + GPA_GET(glGetUniformLocation); + GPA_GET(glUniform2f); + GPA_GET(glUseProgram); + GPA_GET(glVertexAttribPointer); + GPA_GET(glEnableVertexAttribArray); + GPA_GET(glDrawArrays); + + eglBindAPI(EGL_OPENGL_ES_API); + + EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ANativeWindow *previewWindow = android_state->window; + + if(eglDisplay) + { + int major = 0, minor = 0; + eglInitialize(eglDisplay, &major, &minor); + + 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_NONE}; + + EGLint numConfigs; + EGLConfig config; + if(eglChooseConfig(eglDisplay, configAttribs, &config, 1, &numConfigs)) + { + // we only need GLES 2 for this + static const EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + + EGLContext ctx = eglCreateContext(eglDisplay, config, NULL, ctxAttribs); + if(ctx) + { + EGLSurface surface = eglCreateWindowSurface(eglDisplay, config, previewWindow, NULL); + + if(surface) + { + eglMakeCurrent(eglDisplay, surface, surface, ctx); + + // simple pass through shader for the fullscreen triangle verts + const char *vertex = + "attribute vec2 pos;\n" + "void main() { gl_Position = vec4(pos, 0.5, 1.0); }"; + + const char *fragment = R"( +float circle(in vec2 uv, in vec2 centre, in float radius) +{ + return length(uv - centre) - radius; +} + +// distance field for RenderDoc logo +float logo(in vec2 uv) +{ + // add the outer ring + float ret = circle(uv, vec2(0.5, 0.5), 0.275); + + // add the upper arc + ret = min(ret, circle(uv, vec2(0.5, -0.37), 0.71)); + + // subtract the inner ring + ret = max(ret, -circle(uv, vec2(0.5, 0.5), 0.16)); + + // subtract the lower arc + ret = max(ret, -circle(uv, vec2(0.5, -1.085), 1.3)); + + return ret; +} + +uniform vec2 iResolution; + +void main() +{ + vec2 uv = gl_FragCoord.xy / iResolution.xy; + + // centre the UVs in a square. This assumes a landscape layout. + uv.x = 0.5 - (uv.x - 0.5) * (iResolution.x / iResolution.y); + + // this constant here can be tuned depending on DPI to increase AA + float edgeWidth = 10.0/max(iResolution.x, iResolution.y); + + float smoothdist = smoothstep(0.0, edgeWidth, clamp(logo(uv), 0.0, 1.0)); + + // the green is #3bb779 + gl_FragColor = mix(vec4(1.0), vec4(0.2314, 0.7176, 0.4745, 1.0), smoothdist); +} +)"; + + // compile the shaders and link into a program + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, &vertex, NULL); + glCompileShader(vs); + GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, &fragment, NULL); + glCompileShader(fs); + GLuint prog = glCreateProgram(); + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + glUseProgram(prog); + + // set the resolution + GLuint loc = glGetUniformLocation(prog, "iResolution"); + glUniform2f(loc, float(ANativeWindow_getWidth(previewWindow)), + float(ANativeWindow_getHeight(previewWindow))); + + // fullscreen triangle + float verts[] = { + -1.0f, -1.0f, // vertex 0 + 3.0f, -1.0f, // vertex 1 + -1.0f, 3.0f, // vertex 2 + }; + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, verts); + glEnableVertexAttribArray(0); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + eglSwapBuffers(eglDisplay, surface); + } + else + { + ANDROID_LOG("failed making surface"); + } + + eglMakeCurrent(eglDisplay, 0L, 0L, NULL); + eglDestroyContext(eglDisplay, ctx); + eglDestroySurface(eglDisplay, surface); + } + else + { + ANDROID_LOG("failed making context"); + } + } + else + { + ANDROID_LOG("failed choosing config"); + } + } + + dlclose(libEGL); + + pthread_mutex_unlock(&m_DrawLock.lock); } // Returns the renderdoccmd arguments passed via am start @@ -108,18 +309,36 @@ vector getRenderdoccmdArgs() return ret; } +void *cmdthread(void *) +{ + vector args = getRenderdoccmdArgs(); + if(!args.size()) + return NULL; // Nothing for APK to do. + + renderdoccmd(GlobalEnvironment(), args); + + // activity is done and should be closed + ANativeActivity_finish(android_state->activity); + + return NULL; +} + void handle_cmd(android_app *app, int32_t cmd) { switch(cmd) { case APP_CMD_INIT_WINDOW: { - vector args = getRenderdoccmdArgs(); - if(!args.size()) - break; // Nothing for APK to do. - renderdoccmd(GlobalEnvironment(), args); - // activity is done and should be closed - ANativeActivity_finish(android_state->activity); + if(cmdthread_handle == 0) + pthread_create(&cmdthread_handle, NULL, cmdthread, NULL); + + break; + } + case APP_CMD_WINDOW_REDRAW_NEEDED: + case APP_CMD_GAINED_FOCUS: + case APP_CMD_LOST_FOCUS: + { + DisplayGenericSplash(); break; } } @@ -130,8 +349,11 @@ void android_main(struct android_app *state) android_state = state; android_state->onAppCmd = handle_cmd; - __android_log_print(ANDROID_LOG_INFO, LOGCAT_TAG, "android_main android_state->window: %p", - android_state->window); + pthread_mutexattr_init(&m_DrawLock.attr); + pthread_mutexattr_settype(&m_DrawLock.attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_DrawLock.lock, &m_DrawLock.attr); + + ANDROID_LOG("android_main android_state->window: %p", android_state->window); // Used to poll the events in the main loop int events; @@ -144,4 +366,9 @@ void android_main(struct android_app *state) source->process(android_state, source); } } while(android_state->destroyRequested == 0); + + ANDROID_LOG("android_main exiting"); + + pthread_mutex_destroy(&m_DrawLock.lock); + pthread_mutexattr_destroy(&m_DrawLock.attr); }