/* Copyright (c) 2018 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "shell/Shell.h" #include "shell/ShellCallbacks.h" #include "eglshell/EGLShell.h" #include "eglshell/EGLTarget.h" #include #include #include #include #include "shell/ShellBatteryInfo.h" #include "shell/ShellKeyCodes.h" #include "shell/ShellThreads.h" #include #include #define msleep(ms) usleep((ms) * 1000) #define VSYNC_DEFAULT_WINDOW true #define VSYNC_DEFAULT_FULLSCREEN true #define VERTEX_ATTRIB_POSITION 0 #define printf(format, ...) __android_log_print(ANDROID_LOG_INFO, "EGLShell", format, ##__VA_ARGS__); static const char * vertexShaderSource = "#version 300 es\n" "in vec2 inPosition;\n" "uniform mat4 projectionMatrix;\n" "uniform mat4 modelviewMatrix;\n" "void main(void) {\n" " gl_Position = projectionMatrix * modelviewMatrix * vec4(inPosition, 0.0, 1.0);\n" "}"; static const char * fragmentShaderSource = "#version 300 es\n" "uniform vec4 constantColor;\n" "out vec4 fragmentColor;\n" "void main(void) {\n" " fragmentColor = constantColor;\n" "}"; static bool postRedisplayAtEndOfTarget_draw = false; static bool darkClearColor = true; static unsigned int viewportWidth, viewportHeight; static struct { bool active; float x; float y; } touches[32]; static struct { double x; double y; double z; } lastAccelerometerReading; static GLuint shaderProgram; static GLint projectionMatrixUniform; static GLint modelviewMatrixUniform; static GLint constantColorUniform; static unsigned int timer1ID = UINT_MAX, timer2ID = UINT_MAX; static bool deltaMode; static bool syncFullscreen = VSYNC_DEFAULT_FULLSCREEN, syncWindow = VSYNC_DEFAULT_WINDOW; static bool allowQuit = true; static void registerShellCallbacks(); static void unregisterShellCallbacks(); static bool Target_draw() { GLfloat vertices[12]; unsigned int touchIndex; printf("Target_draw()\n"); if (darkClearColor) { glClearColor(0.0f, 0.25f, 0.5f, 0.0f); } else { glClearColor(0.25f, 0.5f, 0.75f, 0.0f); } glClear(GL_COLOR_BUFFER_BIT); GLfloat projectionMatrix[16] = { 2.0f / viewportWidth, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f / viewportHeight, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f }; GLfloat modelviewMatrix[16] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; glUseProgram(shaderProgram); glUniformMatrix4fv(projectionMatrixUniform, 1, GL_FALSE, projectionMatrix); glUniformMatrix4fv(modelviewMatrixUniform, 1, GL_FALSE, modelviewMatrix); if (lastAccelerometerReading.x != 0.0) { vertices[0] = viewportWidth / 3 - 5; vertices[1] = viewportHeight / 2; vertices[2] = viewportWidth / 3 + 5; vertices[3] = viewportHeight / 2; vertices[4] = viewportWidth / 3 + 5; vertices[5] = viewportHeight / 2 + lastAccelerometerReading.x * viewportHeight / 6; vertices[6] = viewportWidth / 3 - 5; vertices[7] = viewportHeight / 2 + lastAccelerometerReading.x * viewportHeight / 6; glVertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glUniform4f(constantColorUniform, 1.0f, 0.0f, 0.0f, 1.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } if (lastAccelerometerReading.y != 0.0) { vertices[0] = viewportWidth / 2 - 5; vertices[1] = viewportHeight / 2; vertices[2] = viewportWidth / 2 + 5; vertices[3] = viewportHeight / 2; vertices[4] = viewportWidth / 2 + 5; vertices[5] = viewportHeight / 2 + lastAccelerometerReading.y * viewportHeight / 6; vertices[6] = viewportWidth / 2 - 5; vertices[7] = viewportHeight / 2 + lastAccelerometerReading.y * viewportHeight / 6; glVertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glUniform4f(constantColorUniform, 0.0f, 1.0f, 0.0f, 1.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } if (lastAccelerometerReading.z != 0.0) { vertices[0] = viewportWidth - viewportWidth / 3 - 5; vertices[1] = viewportHeight / 2; vertices[2] = viewportWidth - viewportWidth / 3 + 5; vertices[3] = viewportHeight / 2; vertices[4] = viewportWidth - viewportWidth / 3 + 5; vertices[5] = viewportHeight / 2 + lastAccelerometerReading.z * viewportHeight / 6; vertices[6] = viewportWidth - viewportWidth / 3 - 5; vertices[7] = viewportHeight / 2 + lastAccelerometerReading.z * viewportHeight / 6; glVertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glUniform4f(constantColorUniform, 0.0f, 0.0f, 1.0f, 1.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } vertices[0] = 0; vertices[1] = 0; vertices[2] = 16; vertices[3] = 0; vertices[4] = 0; vertices[5] = 16; vertices[6] = viewportWidth; vertices[7] = viewportHeight; vertices[8] = viewportWidth - 16; vertices[9] = viewportHeight; vertices[10] = viewportWidth; vertices[11] = viewportHeight - 16; glVertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glUniform4f(constantColorUniform, 1.0f, 1.0f, 1.0f, 1.0f); glDrawArrays(GL_TRIANGLES, 0, 6); for (touchIndex = 0; touchIndex < 32; touchIndex++) { if (touches[touchIndex].active) { vertices[0] = touches[touchIndex].x - 24; vertices[1] = touches[touchIndex].y - 24; vertices[2] = touches[touchIndex].x - 24; vertices[3] = touches[touchIndex].y + 24; vertices[4] = touches[touchIndex].x + 24; vertices[5] = touches[touchIndex].y + 24; vertices[6] = touches[touchIndex].x + 24; vertices[7] = touches[touchIndex].y - 24; glVertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices); glUniform4f(constantColorUniform, 0.5f, 1.0f, 0.5f, 1.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } } if (postRedisplayAtEndOfTarget_draw) { darkClearColor = !darkClearColor; Shell_redisplay(); } return true; } static void Target_resized(unsigned int newWidth, unsigned int newHeight, double referenceTime) { printf("Target_resized(%d, %d, %f)\n", newWidth, newHeight, referenceTime); viewportWidth = newWidth; viewportHeight = newHeight; glViewport(0, 0, newWidth, newHeight); } static void timerCallback(unsigned int timerID, void * context) { printf("timerCallback(timerID, \"%s\")\n", (char *) context); if (timerID == timer2ID) { timer2ID = UINT_MAX; } } static int threadFunc1(void * context) { printf("Secondary thread 1 %p begin\n", Shell_getCurrentThread()); Shell_lockMutex(context); msleep(1000); Shell_unlockMutex(context); printf("Secondary thread 1 %p end\n", Shell_getCurrentThread()); return 0; } static int threadFunc2(void * context) { printf("Secondary thread 2 %p begin\n", Shell_getCurrentThread()); msleep(1000); Shell_postSemaphore(context); printf("Secondary thread 2 %p end\n", Shell_getCurrentThread()); return 0; } static int threadFunc3(void * context) { printf("Secondary thread 3 %p begin\n", Shell_getCurrentThread()); msleep(1000); printf("Secondary thread 3 %p exit\n", Shell_getCurrentThread()); Shell_exitThread(2); printf("Secondary thread 3 %p end (bad!)\n", Shell_getCurrentThread()); return 0; } static void restoreCallbacksTimer(unsigned int timerID, void * context) { registerShellCallbacks(); printf("Restored event callbacks\n"); } static void Target_keyDown(unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, double referenceTime) { printf("Target_keyDown(%u, %u, 0x%X, %s, %f)\n", charCode, keyCode, modifiers, isRepeat ? "true" : "false", referenceTime); // TODO: Rephrase all of this to allow access to test harness functions without using key events if (keyCode == KEY_CODE_Q) { ShellThread thread; ShellMutex mutex; ShellSemaphore semaphore; bool success; int status; printf("Current thread: %p\n\n", Shell_getCurrentThread()); mutex = Shell_createMutex(); printf("Created mutex %p\n", mutex); success = Shell_tryLockMutex(mutex); printf("Lock acquired: %s (expected true)\n", success ? "true" : "false"); success = Shell_tryLockMutex(mutex); printf("Lock acquired: %s (expected true)\n\n", success ? "true" : "false"); Shell_unlockMutex(mutex); Shell_unlockMutex(mutex); thread = Shell_createThread(threadFunc1, mutex); Shell_detachThread(thread); printf("Created thread %p at %f\n", thread, Shell_getCurrentTime()); msleep(500); Shell_lockMutex(mutex); printf("Acquired lock at %f\n\n", Shell_getCurrentTime()); Shell_unlockMutex(mutex); semaphore = Shell_createSemaphore(0); printf("Try wait semaphore: %s (expected false)\n", Shell_tryWaitSemaphore(semaphore) ? "true" : "false"); thread = Shell_createThread(threadFunc2, semaphore); Shell_detachThread(thread); printf("Created thread %p at %f\n", thread, Shell_getCurrentTime()); printf("Try wait semaphore: %s (expected false)\n", Shell_tryWaitSemaphore(semaphore) ? "true" : "false"); Shell_waitSemaphore(semaphore); printf("Acquired semaphore at %f\n\n", Shell_getCurrentTime()); thread = Shell_createThread(threadFunc3, mutex); printf("Created thread %p at %f\n", thread, Shell_getCurrentTime()); status = Shell_joinThread(thread); printf("Joined thread at %f, with status %d\n\n", Shell_getCurrentTime(), status); Shell_disposeMutex(mutex); Shell_disposeSemaphore(semaphore); } else if (keyCode == KEY_CODE_T) { printf("Shell_getCurrentTime(): %f\n", Shell_getCurrentTime()); } else if (keyCode == KEY_CODE_R) { printf("Shell_getResourcePath(): %s\n", Shell_getResourcePath()); } else if (keyCode == KEY_CODE_Y) { printf("Shell_getSupportPath(NULL): %s\n", Shell_getSupportPath(NULL)); printf("Shell_getSupportPath(\"eglshell\"): %s\n", Shell_getSupportPath("eglshell")); } else if (keyCode == KEY_CODE_U) { Shell_openURL("http://ludobloom.com/"); } else if (keyCode == KEY_CODE_A) { Shell_redisplay(); postRedisplayAtEndOfTarget_draw = true; } else if (keyCode == KEY_CODE_S) { postRedisplayAtEndOfTarget_draw = false; } else if (keyCode == KEY_CODE_D) { darkClearColor = !darkClearColor; Shell_redisplay(); } else if (keyCode == KEY_CODE_E) { unsigned int displayIndex = Shell_getDisplayIndexFromWindow(); printf("Shell_enterFullScreen(%u, false): %s\n", displayIndex, Shell_enterFullScreen(displayIndex, false) ? "true" : "false"); } else if (keyCode == KEY_CODE_W) { Shell_exitFullScreen(); } else if (keyCode == KEY_CODE_G) { printf("Shell_isFullScreen(): %s\n", Shell_isFullScreen() ? "true" : "false"); } else if (keyCode == KEY_CODE_Z) { EGLShell_setAccelerometerInterval(0); lastAccelerometerReading.x = lastAccelerometerReading.y = lastAccelerometerReading.z = 0.0; Shell_redisplay(); } else if (keyCode == KEY_CODE_V) { EGLShell_setAccelerometerInterval(1.0 / 60.0); } else if (keyCode == KEY_CODE_C) { EGLShell_setAccelerometerInterval(1.0 / 10.0); } else if (keyCode == KEY_CODE_B) { printf("Shell_getBatteryState(): %d\n", Shell_getBatteryState()); printf("Shell_getBatteryLevel(): %f\n", Shell_getBatteryLevel()); } else if (keyCode == KEY_CODE_Z) { int x = 0, y = 0; unsigned int width = 0, height = 0; static unsigned int screenIndex; screenIndex %= Shell_getDisplayCount(); Shell_getSafeWindowRect(screenIndex, &x, &y, &width, &height); printf("Shell_getSafeWindowRect(%u): %d, %d, %u, %u\n", screenIndex, x, y, width, height); screenIndex++; } else if (keyCode == KEY_CODE_X) { int x = 0, y = 0; unsigned int width = 0, height = 0; static unsigned int screenIndex; screenIndex %= Shell_getDisplayCount(); Shell_getDisplayBounds(screenIndex, &x, &y, &width, &height); printf("Shell_getDisplayBounds(%u): %d, %d, %u, %u\n", screenIndex, x, y, width, height); screenIndex++; } else if (keyCode == KEY_CODE_C) { printf("Shell_getDisplayCount(): %u\n", Shell_getDisplayCount()); } else if (keyCode == KEY_CODE_V) { bool sync, fullscreen; fullscreen = Shell_isFullScreen(); if (fullscreen) { syncFullscreen = !syncFullscreen; sync = syncFullscreen; } else { syncWindow = !syncWindow; sync = syncWindow; } Shell_setVSync(sync, fullscreen); printf("Shell_setVSync(%s, %s)\n", sync ? "true" : "false", fullscreen ? "true" : "false"); } else if (keyCode == KEY_CODE_COMMA) { if (timer1ID == UINT_MAX) { timer1ID = Shell_setTimer(1.0, true, timerCallback, "Timer 1 context"); printf("Shell_setTimer(1.0, true, %p, \"Timer 1 context\"): %u\n", timerCallback, timer1ID); } else { printf("Shell_cancelTimer(%u)\n", timer1ID); Shell_cancelTimer(timer1ID); timer1ID = UINT_MAX; } } else if (keyCode == KEY_CODE_PERIOD) { if (timer2ID == UINT_MAX) { timer2ID = Shell_setTimer(2.0, false, timerCallback, "Timer 2 context"); printf("Shell_setTimer(2.0, false, %p, \"Timer 2 context\"): %u\n", timerCallback, timer2ID); } else { printf("Shell_cancelTimer(%u)\n", timer2ID); Shell_cancelTimer(timer2ID); timer2ID = UINT_MAX; } } else if (keyCode == KEY_CODE_SEMICOLON) { deltaMode = !deltaMode; Shell_setMouseDeltaMode(deltaMode); printf("Shell_setMouseDeltaMode(%s)\n", deltaMode ? "true" : "false"); } else if (keyCode == KEY_CODE_BACKSPACE) { unregisterShellCallbacks(); printf("Removed all event callbacks for 5 seconds\n"); Shell_setTimer(5.0, false, restoreCallbacksTimer, NULL); } else if (keyCode == KEY_CODE_SPACE) { Shell_systemBeep(); } else if (keyCode == KEY_CODE_N) { //EGLShell_setBatteryMonitoringEnabled(false); } else if (keyCode == KEY_CODE_M) { //EGLShell_setBatteryMonitoringEnabled(true); } else if (keyCode == KEY_CODE_ENTER) { //EGLShell_hideKeyboard(); } } static void Target_keyUp(unsigned int keyCode, unsigned int modifiers, double referenceTime) { printf("Target_keyUp(%u, 0x%X, %f)\n", keyCode, modifiers, referenceTime); } static void Target_keyModifiersChanged(unsigned int modifiers, unsigned int lastModifiers, double referenceTime) { printf("Target_keyModifiersChanged(0x%X, 0x%X, %f)\n", modifiers, lastModifiers, referenceTime); } static void Target_mouseDown(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) { printf("Target_mouseDown(%d, 0x%X, %f, %f, 0x%X, %f)\n", buttonNumber, buttonMask, x, y, modifiers, referenceTime); touches[buttonNumber].active = true; touches[buttonNumber].x = x; touches[buttonNumber].y = y; Shell_redisplay(); } static void Target_mouseUp(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifiers, double referenceTime) { printf("Target_mouseUp(%d, 0x%X, %f, %f, 0x%X, %f)\n", buttonNumber, buttonMask, x, y, modifiers, referenceTime); //EGLShell_showKeyboard(); touches[buttonNumber].active = false; touches[buttonNumber].x = x; touches[buttonNumber].y = y; Shell_redisplay(); if (y < 200) { if (Shell_isFullScreen()) { Shell_exitFullScreen(); } else { Shell_enterFullScreen(0, false); } } } static unsigned int lowestBitIndex(unsigned int value) { // See http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightModLookup static const int mod37BitPosition[] = { 32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18 }; return mod37BitPosition[(-value & value) % 37]; } static void Target_mouseDragged(unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifiers, double referenceTime) { printf("Target_mouseDragged(0x%X, %f, %f, %f, %f, 0x%X, %f)\n", buttonMask, x, y, deltaX, deltaY, modifiers, referenceTime); touches[lowestBitIndex(buttonMask)].x = x; touches[lowestBitIndex(buttonMask)].y = y; Shell_redisplay(); } static void Target_scrollWheel(float x, float y, int deltaX, int deltaY, unsigned int modifiers, double referenceTime) { printf("Target_scrollWheel(%f, %f, %d, %d, 0x%X, %f)\n", x, y, deltaX, deltaY, modifiers, referenceTime); } static void Target_backgrounded(double referenceTime) { printf("Target_backgrounded(%f)\n", referenceTime); } static void Target_foregrounded(double referenceTime) { printf("Target_foregrounded(%f)\n", referenceTime); } static bool Target_confirmQuit() { printf("Target_confirmQuit() (returning %s)\n", allowQuit ? "true" : "false"); return allowQuit; } static void EGLTarget_touchesCanceled(unsigned int buttonNumber, unsigned int buttonMask, double referenceTime) { printf("EGLTarget_touchesCanceled(%u, 0x%X, %f)\n", buttonNumber, buttonMask, referenceTime); touches[buttonNumber].active = false; } static void EGLTarget_accelerometer(double x, double y, double z) { lastAccelerometerReading.x = x; lastAccelerometerReading.y = y; lastAccelerometerReading.z = z; Shell_redisplay(); } static unsigned int configAlphaSize = 0; static unsigned int configDepthSize = 0; static unsigned int configStencilBuffer = false; static void EGLTarget_openURL(const char * url) { printf("Got URL: %s\n", url); sscanf(url, "eglshell://%u;%u;%u", &configAlphaSize, &configDepthSize, &configStencilBuffer); } static void registerShellCallbacks() { Shell_drawFunc(Target_draw); Shell_resizeFunc(Target_resized); Shell_keyDownFunc(Target_keyDown); Shell_keyUpFunc(Target_keyUp); Shell_keyModifiersChangedFunc(Target_keyModifiersChanged); Shell_mouseDownFunc(Target_mouseDown); Shell_mouseUpFunc(Target_mouseUp); Shell_mouseDraggedFunc(Target_mouseDragged); Shell_scrollWheelFunc(Target_scrollWheel); Shell_backgroundedFunc(Target_backgrounded); Shell_foregroundedFunc(Target_foregrounded); Shell_confirmQuitFunc(Target_confirmQuit); EGLShell_openURLFunc(EGLTarget_openURL); EGLShell_touchesCanceledFunc(EGLTarget_touchesCanceled); EGLShell_accelerometerFunc(EGLTarget_accelerometer); } static void unregisterShellCallbacks() { Shell_drawFunc(NULL); Shell_resizeFunc(NULL); Shell_keyDownFunc(NULL); Shell_keyUpFunc(NULL); Shell_keyModifiersChangedFunc(NULL); Shell_mouseDownFunc(NULL); Shell_mouseUpFunc(NULL); Shell_mouseMovedFunc(NULL); Shell_mouseDraggedFunc(NULL); Shell_scrollWheelFunc(NULL); Shell_backgroundedFunc(NULL); Shell_foregroundedFunc(NULL); Shell_confirmQuitFunc(NULL); EGLShell_openURLFunc(NULL); EGLShell_touchesCanceledFunc(NULL); EGLShell_accelerometerFunc(NULL); } void EGLTarget_configure(struct EGLShellConfiguration * configuration) { printf("EGLTarget_configure(%p)\n", configuration); printf("configuration->displayMode.alphaSize: %u\n", configuration->displayMode.alphaSize); printf("configuration->displayMode.depthSize: %u\n", configuration->displayMode.depthSize); printf("configuration->displayMode.stencilBuffer: %s\n", configuration->displayMode.stencilBuffer ? "true" : "false"); configAlphaSize = 8; configDepthSize = 24; configStencilBuffer = true; configuration->displayMode.alphaSize = configAlphaSize; printf("configuration->displayMode.alphaSize = %u\n", configuration->displayMode.alphaSize); configuration->displayMode.depthSize = configDepthSize; printf("configuration->displayMode.depthSize = %u\n", configuration->displayMode.depthSize); configuration->displayMode.stencilBuffer = configStencilBuffer; printf("configuration->displayMode.stencilBuffer = %s\n", configuration->displayMode.stencilBuffer ? "true" : "false"); registerShellCallbacks(); } void Target_init() { GLuint vertexShader, fragmentShader; printf("Target_init()\n"); shaderProgram = glCreateProgram(); vertexShader = glCreateShader(GL_VERTEX_SHADER); fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vertexShader, 1, (const GLchar **) &vertexShaderSource, NULL); glShaderSource(fragmentShader, 1, (const GLchar **) &fragmentShaderSource, NULL); glCompileShader(vertexShader); glCompileShader(fragmentShader); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glBindAttribLocation(shaderProgram, VERTEX_ATTRIB_POSITION, "inPosition"); glLinkProgram(shaderProgram); projectionMatrixUniform = glGetUniformLocation(shaderProgram, "projectionMatrix"); modelviewMatrixUniform = glGetUniformLocation(shaderProgram, "modelviewMatrix"); constantColorUniform = glGetUniformLocation(shaderProgram, "constantColor"); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); glEnableVertexAttribArray(VERTEX_ATTRIB_POSITION); Shell_mainLoop(); }