/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author: Daniel van Vugt */ #include "eglapp.h" #include #include #include #include #include #include #include #include enum { max_fingers = 10, max_samples_per_frame = 1000 }; typedef struct { float x, y; } Vec2; typedef struct { int samples; Vec2 sample[max_samples_per_frame]; } Finger; typedef struct { int fingers; Finger finger[max_fingers]; } TouchState; typedef struct { sigset_t sigs; pthread_mutex_t mutex; pthread_cond_t change_cv; bool changed; bool running; bool resized; TouchState touch; } State; static GLuint load_shader(const char *src, GLenum type) { GLuint shader = glCreateShader(type); if (shader) { GLint compiled; glShaderSource(shader, 1, &src, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLchar log[1024]; glGetShaderInfoLog(shader, sizeof log - 1, NULL, log); log[sizeof log - 1] = '\0'; printf("load_shader compile failed: %s\n", log); glDeleteShader(shader); shader = 0; } } return shader; } GLuint generate_target_texture() { const int width = 512, height = width; typedef struct { GLubyte r, b, g, a; } Texel; Texel image[height][width]; // Note the 0.5f to convert from pixel corner (GL) to middle (image) const float centrex = width/2 - 0.5f, centrey = height/2 - 0.5f; const Texel blank = {0, 0, 0, 0}; const int radius = centrex - 1; const Texel ring[] = { { 0, 0, 0, 255}, { 0, 0, 255, 255}, { 0, 255, 0, 255}, {255, 255, 0, 255}, {255, 128, 0, 255}, {255, 0, 0, 255}, }; const int rings = sizeof(ring) / sizeof(ring[0]); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float dx = x - centrex, dy = y - centrey; int layer = rings * sqrtf(dx * dx + dy * dy) / radius; image[y][x] = layer < rings ? ring[layer] : blank; } } GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); return tex; } static void get_all_touch_points(const MirInputEvent *ievent, TouchState *touch) { if (mir_input_event_get_type(ievent) == mir_input_event_type_pointer) { const MirPointerEvent *pevent = mir_input_event_get_pointer_event(ievent); if (mir_pointer_event_action(pevent) == mir_pointer_action_leave) { touch->fingers = 0; } else if (touch->finger[0].samples < max_samples_per_frame) { if (!touch->fingers) touch->finger[0].samples = 0; touch->fingers = 1; touch->finger[0].sample[touch->finger[0].samples++] = (Vec2) { mir_pointer_event_axis_value(pevent, mir_pointer_axis_x), mir_pointer_event_axis_value(pevent, mir_pointer_axis_y) }; } } else if (mir_input_event_get_type(ievent) == mir_input_event_type_touch) { const MirTouchEvent *tevent = mir_input_event_get_touch_event(ievent); int n = mir_touch_event_point_count(tevent); if (n > max_fingers) n = max_fingers; for (int f = 0; f < n; ++f) { Finger *finger = touch->finger + f; if (f >= touch->fingers) { finger->samples = 0; touch->fingers = f + 1; } if (mir_touch_event_action(tevent, f) == mir_touch_action_up) { finger->samples = 0; continue; } if (finger->samples >= max_samples_per_frame) continue; finger->sample[finger->samples++] = (Vec2) { mir_touch_event_axis_value(tevent, f, mir_touch_axis_x), mir_touch_event_axis_value(tevent, f, mir_touch_axis_y) }; } } } static void on_event(MirWindow *surface, const MirEvent *event, void *context) { (void)surface; State *state = (State*)context; // FIXME: We presently need to know that events come in on a different // thread to main (LP: #1194384). When that's resolved, simple // single-threaded apps like this won't need pthread. pthread_mutex_lock(&state->mutex); switch (mir_event_get_type(event)) { case mir_event_type_input: get_all_touch_points(mir_event_get_input_event(event), &state->touch); break; case mir_event_type_resize: state->resized = true; break; case mir_event_type_close_window: state->running = false; break; default: break; } state->changed = true; pthread_cond_signal(&state->change_cv); pthread_mutex_unlock(&state->mutex); } static void* shutdown_handler(void* context) { State *state = (State*)context; int signum; sigwait(&state->sigs, &signum); printf("Signal %d received. Good night.\n", signum); pthread_mutex_lock(&state->mutex); state->running = false; state->changed = true; pthread_cond_signal(&state->change_cv); pthread_mutex_unlock(&state->mutex); return NULL; } int main(int argc, char *argv[]) { const char vshadersrc[] = "attribute vec2 position;\n" "attribute vec2 texcoord;\n" "uniform float scale;\n" "uniform vec2 translate;\n" "uniform mat4 projection;\n" "varying vec2 v_texcoord;\n" "\n" "void main()\n" "{\n" " gl_Position = projection *\n" " vec4(position * scale + translate, 0.0, 1.0);\n" " v_texcoord = texcoord;\n" "}\n"; const char fshadersrc[] = "precision mediump float;\n" "varying vec2 v_texcoord;\n" "uniform sampler2D texture;\n" "uniform float opacity;\n" "\n" "void main()\n" "{\n" " vec4 f = texture2D(texture, v_texcoord);\n" " f.a *= opacity;\n" " gl_FragColor = f;\n" "}\n"; sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGHUP); pthread_sigmask(SIG_BLOCK, &sigs, NULL); static unsigned int width = 0, height = 0; if (!mir_eglapp_init(argc, argv, &width, &height, NULL)) return 1; GLuint vshader = load_shader(vshadersrc, GL_VERTEX_SHADER); assert(vshader); GLuint fshader = load_shader(fshadersrc, GL_FRAGMENT_SHADER); assert(fshader); GLuint prog = glCreateProgram(); assert(prog); glAttachShader(prog, vshader); glAttachShader(prog, fshader); glLinkProgram(prog); GLint linked; glGetProgramiv(prog, GL_LINK_STATUS, &linked); if (!linked) { GLchar log[1024]; glGetProgramInfoLog(prog, sizeof log - 1, NULL, log); log[sizeof log - 1] = '\0'; printf("Link failed: %s\n", log); return 2; } glUseProgram(prog); const GLfloat square[] = { // position texcoord -0.5f, +0.5f, 0.0f, 1.0f, +0.5f, +0.5f, 1.0f, 1.0f, +0.5f, -0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.0f, 0.0f, }; GLint position = glGetAttribLocation(prog, "position"); GLint texcoord = glGetAttribLocation(prog, "texcoord"); glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), square); glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), square+2); glEnableVertexAttribArray(position); glEnableVertexAttribArray(texcoord); GLint scale = glGetUniformLocation(prog, "scale"); glUniform1f(scale, 128.0f); GLint opacity = glGetUniformLocation(prog, "opacity"); glUniform1f(opacity, 1.0f); GLint translate = glGetUniformLocation(prog, "translate"); glUniform2f(translate, 0.0f, 0.0f); GLint projection = glGetUniformLocation(prog, "projection"); GLuint tex = generate_target_texture(); glBindTexture(GL_TEXTURE_2D, tex); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glViewport(0, 0, width, height); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Behave like an accumulation buffer State state = { .mutex = PTHREAD_MUTEX_INITIALIZER, .change_cv = PTHREAD_COND_INITIALIZER, .changed = true, .running = true, .resized = true, .touch = {0, {{0, {{0.0f, 0.0f}}}}} }; state.sigs = sigs; pthread_t shutdown_handler_thread; if (pthread_create(&shutdown_handler_thread, NULL, &shutdown_handler, &state)) { printf("Failed creating shutdown handling thread\n"); return 3; } MirWindow* window = mir_eglapp_native_window(); mir_window_set_event_handler(window, on_event, &state); while (true) { pthread_mutex_lock(&state.mutex); while (state.running && !state.changed) pthread_cond_wait(&state.change_cv, &state.mutex); if (!state.running) { pthread_mutex_unlock(&state.mutex); break; } if (state.resized) { // mir_eglapp_swap_buffers updates the viewport for us... GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); int w = viewport[2], h = viewport[3]; // TRANSPOSED projection matrix to convert from the Mir input // rectangle {{0,0},{w,h}} to GL screen rectangle {{-1,1},{2,2}}. GLfloat matrix[16] = {2.0f/w, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f/h, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f}; // Note GL_FALSE: GLES does not support the transpose option glUniformMatrix4fv(projection, 1, GL_FALSE, matrix); state.resized = false; } glClear(GL_COLOR_BUFFER_BIT); for (int f = 0; f < state.touch.fingers; ++f) { const Finger *finger = state.touch.finger + f; if (!finger->samples) continue; glUniform1f(opacity, 1.0f / finger->samples); for (int s = 0; s < finger->samples; ++s) { // Note the 0.5f to convert from pixel middle to corner (GL) glUniform2f(translate, finger->sample[s].x + 0.5f, finger->sample[s].y + 0.5f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } } // Just keep the latest sample for the zeroth finger (mouse pointer) if (state.touch.fingers) { state.touch.finger[0].sample[0] = state.touch.finger[0].sample[state.touch.finger[0].samples-1]; state.touch.finger[0].samples = 1; state.touch.fingers = 1; } // Put the event loop back to sleep: state.changed = false; pthread_mutex_unlock(&state.mutex); mir_eglapp_swap_buffers(); } mir_window_set_event_handler(window, NULL, NULL); mir_eglapp_cleanup(); pthread_join(shutdown_handler_thread, NULL); return 0; }