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);
}