diff --git a/CMakeLists.txt b/CMakeLists.txt index 170a2d5..05a4d57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,13 +29,20 @@ pkg_check_modules(XLIB x11-xcb) pkg_check_modules(LIBVA vdpau) pkg_check_modules(PULSE libpulse-simple) pkg_check_modules(CEC libcec>=3.0.0) +pkg_check_modules(EGL egl) +pkg_check_modules(GLES glesv2) -if(AVCODEC_FOUND AND AVUTIL_FOUND AND SDL_FOUND) - set(SOFTWARE_FOUND TRUE) - if(XLIB_FOUND AND LIBVA_FOUND) - set(VDPAU_FOUND TRUE) - else() - set(VDPAU_FOUND FALSE) +if(AVCODEC_FOUND AND AVUTIL_FOUND) + if(EGL_FOUND AND GLES_FOUND AND XLIB_FOUND) + set(X11_FOUND TRUE) + endif() + if(SDL_FOUND OR X11_FOUND) + set(SOFTWARE_FOUND TRUE) + if(XLIB_FOUND AND LIBVA_FOUND) + set(VDPAU_FOUND TRUE) + else() + set(VDPAU_FOUND FALSE) + endif() endif() else() set(SOFTWARE_FOUND FALSE) @@ -52,9 +59,17 @@ elseif(NOT AMLOGIC_FOUND AND NOT BROADCOM_FOUND AND NOT FREESCALE_FOUND AND NOT endif() if (SOFTWARE_FOUND) - list(APPEND SRC_LIST ./src/video/ffmpeg.c ./src/video/sdl.c ./src/audio/sdl.c) - list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) - list(APPEND MOONLIGHT_OPTIONS SDL) + list(APPEND SRC_LIST ./src/video/ffmpeg.c) + if (SDL_FOUND) + list(APPEND SRC_LIST ./src/video/sdl.c ./src/audio/sdl.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_SDL) + list(APPEND MOONLIGHT_OPTIONS SDL) + endif() + if (X11_FOUND) + list(APPEND SRC_LIST ./src/video/x11.c ./src/video/egl.c ./src/input/x11.c) + list(APPEND MOONLIGHT_DEFINITIONS HAVE_X11) + list(APPEND MOONLIGHT_OPTIONS X11) + endif() if(VDPAU_FOUND) list(APPEND SRC_LIST ./src/video/ffmpeg_vdpau.c) list(APPEND MOONLIGHT_DEFINITIONS HAVE_VDPAU) @@ -120,9 +135,19 @@ if(FREESCALE_FOUND) install(TARGETS moonlight-imx DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() +if(SDL_FOUND) + target_include_directories(moonlight PRIVATE ${SDL_INCLUDE_DIRS}) + target_link_libraries(moonlight ${SDL_LIBRARIES}) +endif() + +if(X11_FOUND) + target_include_directories(moonlight PRIVATE ${XLIB_INCLUDE_DIRS} ${EGL_INCLUDE_DIRS} ${GLES_INCLUDE_DIRS}) + target_link_libraries(moonlight ${XLIB_LIBRARIES} ${EGL_LIBRARIES} ${GLES_LIBRARIES}) +endif() + if (SOFTWARE_FOUND) - target_include_directories(moonlight PRIVATE ${SDL_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) - target_link_libraries(moonlight ${SDL_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES}) + target_include_directories(moonlight PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) + target_link_libraries(moonlight ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES}) if(VDPAU_FOUND) target_include_directories(moonlight PRIVATE ${XLIB_INCLUDE_DIRS} ${LIBVA_INCLUDE_DIRS}) target_link_libraries(moonlight ${XLIB_LIBRARIES} ${LIBVA_LIBRARIES}) diff --git a/src/input/x11.c b/src/input/x11.c new file mode 100644 index 0000000..e661efe --- /dev/null +++ b/src/input/x11.c @@ -0,0 +1,146 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "x11.h" +#include "keyboard.h" +#include "../global.h" +#include "../loop.h" + +#include + +#include +#include + +#include +#include +#include + +#define MODIFIERS (MODIFIER_SHIFT|MODIFIER_ALT|MODIFIER_CTRL) + +static Display *display; +static Window window; + +static Atom wm_deletemessage; + +static int last_x = -1, last_y = -1; +static int keyboard_modifiers; + +static const char data[1] = {0}; +static Cursor cursor; +static bool grabbed = True; + +static int x11_handler(int fd) { + XEvent event; + int button = 0; + int motion_x, motion_y; + + XNextEvent(display, &event); + switch (event.type) { + case KeyPress: + case KeyRelease: + if (event.xkey.keycode >= 8 && event.xkey.keycode < (sizeof(keyCodes)/sizeof(keyCodes[0]) + 8)) { + if ((keyboard_modifiers & MODIFIERS) == MODIFIERS && event.type == KeyRelease) { + grabbed = !grabbed; + XDefineCursor(display, window, grabbed ? cursor : NULL); + } + + int modifier = 0; + switch (event.xkey.keycode) { + case 0x32: + case 0x3e: + modifier = MODIFIER_SHIFT; + break; + case 0x40: + case 0x6c: + modifier = MODIFIER_ALT; + break; + case 0x25: + case 0x69: + modifier = MODIFIER_CTRL; + break; + } + + if (modifier != 0) { + if (event.type == KeyPress) + keyboard_modifiers |= modifier; + else + keyboard_modifiers &= ~modifier; + } + + short code = 0x80 << 8 | keyCodes[event.xkey.keycode - 8]; + LiSendKeyboardEvent(code, event.type == KeyPress ? KEY_ACTION_DOWN : KEY_ACTION_UP, keyboard_modifiers); + } + break; + case ButtonPress: + case ButtonRelease: + switch (event.xbutton.button) { + case Button1: + button = BUTTON_LEFT; + break; + case Button2: + button = BUTTON_MIDDLE; + break; + case Button3: + button = BUTTON_RIGHT; + break; + } + + if (button != 0) + LiSendMouseButtonEvent(event.type==ButtonPress ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, button); + break; + case MotionNotify: + motion_x = event.xmotion.x - last_x; + motion_y = event.xmotion.y - last_y; + if (abs(motion_x) > 0 || abs(motion_y) > 0) { + if (last_x >= 0 && last_y >= 0) + LiSendMouseMoveEvent(motion_x, motion_y); + + if (grabbed) + XWarpPointer(display, None, window, 0, 0, 0, 0, 640, 360); + } + + last_x = grabbed ? 640 : event.xmotion.x; + last_y = grabbed ? 360 : event.xmotion.y; + break; + case ClientMessage: + if (event.xclient.data.l[0] == wm_deletemessage) + quit(); + + break; + } + + return LOOP_OK; +} + +void x11_input_init(Display* x11_display, Window x11_window) { + display = x11_display; + window = x11_window; + + wm_deletemessage = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &wm_deletemessage, 1); + + /* make a blank cursor */ + XColor dummy; + Pixmap blank = XCreateBitmapFromData(display, window, data, 1, 1); + cursor = XCreatePixmapCursor(display, blank, blank, &dummy, &dummy, 0, 0); + XFreePixmap(display, blank); + XDefineCursor(display, window, cursor); + + loop_add_fd(ConnectionNumber(display), x11_handler, POLLIN | POLLERR | POLLHUP); +} diff --git a/src/input/x11.h b/src/input/x11.h new file mode 100644 index 0000000..86f6f21 --- /dev/null +++ b/src/input/x11.h @@ -0,0 +1,22 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include + +void x11_input_init(Display* display, Window window); diff --git a/src/platform.c b/src/platform.c index 951d853..e4ea788 100644 --- a/src/platform.c +++ b/src/platform.c @@ -55,6 +55,10 @@ enum platform platform_check(char* name) { return AML; } #endif + #ifdef HAVE_X11 + if (std || strcmp(name, "x11") == 0) + return X11; + #endif #ifdef HAVE_SDL if (std || strcmp(name, "sdl") == 0) return SDL; @@ -67,6 +71,10 @@ enum platform platform_check(char* name) { DECODER_RENDERER_CALLBACKS* platform_get_video(enum platform system) { switch (system) { + #ifdef HAVE_X11 + case X11: + return &decoder_callbacks_x11; + #endif #ifdef HAVE_SDL case SDL: return &decoder_callbacks_sdl; diff --git a/src/platform.h b/src/platform.h index 8e9ee8c..d9aa03e 100644 --- a/src/platform.h +++ b/src/platform.h @@ -26,7 +26,7 @@ #define IS_EMBEDDED(SYSTEM) SYSTEM != SDL -enum platform { NONE, SDL, PI, IMX, AML, FAKE }; +enum platform { NONE, SDL, X11, PI, IMX, AML, FAKE }; enum platform platform_check(char*); PDECODER_RENDERER_CALLBACKS platform_get_video(enum platform system); @@ -35,6 +35,9 @@ bool platform_supports_hevc(enum platform system); extern DECODER_RENDERER_CALLBACKS decoder_callbacks_fake; extern AUDIO_RENDERER_CALLBACKS audio_callbacks_fake; +#ifdef HAVE_X11 +extern DECODER_RENDERER_CALLBACKS decoder_callbacks_x11; +#endif #ifdef HAVE_SDL extern DECODER_RENDERER_CALLBACKS decoder_callbacks_sdl; void sdl_loop(); diff --git a/src/video/egl.c b/src/video/egl.c new file mode 100644 index 0000000..0486e85 --- /dev/null +++ b/src/video/egl.c @@ -0,0 +1,199 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "egl.h" + +#include + +#include + +#include +#include +#include +#include +#include + +static const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; +static const char* texture_mappings[] = { "ymap", "umap", "vmap" }; +static const char* vertex_source = "\ +attribute vec2 position;\ +varying mediump vec2 tex_position;\ +\ +void main() {\ + gl_Position = vec4(position, 0, 1);\ + tex_position = vec2((position.x + 1.) / 2., (1. - position.y) / 2.);\ +}\ +"; + +static const char* fragment_source = "\ +uniform lowp sampler2D ymap;\ +uniform lowp sampler2D umap;\ +uniform lowp sampler2D vmap;\ +varying mediump vec2 tex_position;\ +\ +void main() {\ + mediump float y = texture2D(ymap, tex_position).r;\ + mediump float u = texture2D(umap, tex_position).r - .5;\n\ + mediump float v = texture2D(vmap, tex_position).r - .5;\n\ + lowp float r = y + 1.28033 * v;\ + lowp float g = y - .21482 * u - .38059 * v;\ + lowp float b = y + 2.12798 * u;\ + gl_FragColor = vec4(r, g, b, 1.0);\ +}\ +"; + +static const float vertices[] = { + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f +}; + +static const GLuint elements[] = { + 0, 1, 2, + 2, 3, 0 +}; + +static EGLDisplay display; +static EGLSurface surface; +static EGLContext context; + +static int width, height; +static bool current; + +static GLuint texture_id[3], texture_uniform[3]; +static GLuint shader_program; + +void egl_init(EGLNativeDisplayType native_display, NativeWindowType native_window, int display_width, int display_height) { + width = display_width; + height = display_height; + + // get an EGL display connection + display = eglGetDisplay(native_display); + if (display == EGL_NO_DISPLAY) { + fprintf( stderr, "EGL: error get display\n" ); + exit(EXIT_FAILURE); + } + + // initialize the EGL display connection + int major, minor; + EGLBoolean result = eglInitialize(display, &major, &minor); + if (result == EGL_FALSE) { + fprintf( stderr, "EGL: error initialising display\n"); + exit(EXIT_FAILURE); + } + + // get our config from the config class + EGLConfig config = NULL; + static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; + + EGLint totalConfigsFound = 0; + result = eglChooseConfig(display, attribute_list, &config, 1, &totalConfigsFound); + if (result != EGL_TRUE || totalConfigsFound == 0) { + fprintf(stderr, "EGL: Unable to query for available configs, found %d.\n", totalConfigsFound); + exit(EXIT_FAILURE); + } + + // bind the OpenGL API to the EGL + result = eglBindAPI(EGL_OPENGL_ES_API); + if (result == EGL_FALSE) { + fprintf(stderr, "EGL: error binding API\n"); + exit(EXIT_FAILURE); + } + + // create an EGL rendering context + context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes); + if (context == EGL_NO_CONTEXT) { + fprintf(stderr, "EGL: couldn't get a valid context\n"); + exit(EXIT_FAILURE); + } + + // finally we can create a new surface using this config and window + surface = eglCreateWindowSurface(display, config, (NativeWindowType) native_window, NULL); + eglMakeCurrent(display, surface, surface, context); + + glEnable(GL_TEXTURE_2D); + + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + GLuint ebo; + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW); + + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vertex_source, NULL); + glCompileShader(vertex_shader); + GLint maxLength = 0; + + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_source, NULL); + glCompileShader(fragment_shader); + + shader_program = glCreateProgram(); + glAttachShader(shader_program, vertex_shader); + glAttachShader(shader_program, fragment_shader); + + glLinkProgram(shader_program); + glBindAttribLocation(shader_program, 0, "position"); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); + + glGenTextures(3, texture_id); + for (int i = 0; i < 3; i++) { + glBindTexture(GL_TEXTURE_2D, texture_id[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, i > 0 ? width / 2 : width, i > 0 ? height / 2 : height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0); + + texture_uniform[i] = glGetUniformLocation(shader_program, texture_mappings[i]); + } + + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +void egl_draw(const uint8_t* image[3]) { + if (!current) { + eglMakeCurrent(display, surface, surface, context); + current = True; + } + + glUseProgram(shader_program); + glEnableVertexAttribArray(0); + + for (int i = 0; i < 3; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, texture_id[i]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, i > 0 ? width / 2 : width, i > 0 ? height / 2 : height, GL_LUMINANCE, GL_UNSIGNED_BYTE, image[i]); + glUniform1i(texture_uniform[i], i); + } + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + eglSwapBuffers(display, surface); +} + +void egl_destroy() { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); +} diff --git a/src/video/egl.h b/src/video/egl.h new file mode 100644 index 0000000..fe6b800 --- /dev/null +++ b/src/video/egl.h @@ -0,0 +1,24 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include + +void egl_init(EGLNativeDisplayType native_display, NativeWindowType native_window, int display_width, int display_height); +void egl_draw(const uint8_t* image[3]); +void egl_destroy(); diff --git a/src/video/x11.c b/src/video/x11.c new file mode 100644 index 0000000..d1c591e --- /dev/null +++ b/src/video/x11.c @@ -0,0 +1,115 @@ +/* + * This file is part of Moonlight Embedded. + * + * Copyright (C) 2017 Iwan Timmer + * + * Moonlight is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Moonlight 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 Moonlight; if not, see . + */ + +#include "../video.h" +#include "../input/x11.h" +#include "egl.h" +#include "ffmpeg.h" + +#include + +#include +#include + +#include + +#define DECODER_BUFFER_SIZE 92*1024 + +static char* ffmpeg_buffer = NULL; + +static Display *display; + +void x11_setup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) { + int avc_flags = SLICE_THREADING; + if (drFlags & FORCE_HARDWARE_ACCELERATION) + avc_flags |= HARDWARE_ACCELERATION; + + if (ffmpeg_init(videoFormat, width, height, avc_flags, 2, 2) < 0) { + fprintf(stderr, "Couldn't initialize video decoding\n"); + exit(1); + } + + ffmpeg_buffer = malloc(DECODER_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + if (ffmpeg_buffer == NULL) { + fprintf(stderr, "Not enough memory\n"); + exit(1); + } + + XInitThreads(); + display = XOpenDisplay(NULL); + if (!display) { + fprintf(stderr, "Error: failed to open X display.\n"); + return; + } + + Window root = DefaultRootWindow(display); + XSetWindowAttributes winattr = { .event_mask = PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask }; + Window window = XCreateWindow(display, root, 0, 0, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask, &winattr); + XMapWindow(display, window); + XStoreName(display, window, "Moonlight"); + + if (drFlags & DISPLAY_FULLSCREEN) { + Atom wm_state = XInternAtom(display, "_NET_WM_STATE", False); + Atom fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False); + + XEvent xev = {0}; + xev.type = ClientMessage; + xev.xclient.window = window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = fullscreen; + xev.xclient.data.l[2] = 0; + + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } + + egl_init(display, window, width, height); + x11_input_init(display, window); +} + +void x11_cleanup() { + ffmpeg_destroy(); + egl_destroy(); +} + +int x11_submit_decode_unit(PDECODE_UNIT decodeUnit) { + if (decodeUnit->fullLength < DECODER_BUFFER_SIZE) { + PLENTRY entry = decodeUnit->bufferList; + int length = 0; + while (entry != NULL) { + memcpy(ffmpeg_buffer+length, entry->data, entry->length); + length += entry->length; + entry = entry->next; + } + ffmpeg_decode(ffmpeg_buffer, length); + AVFrame* frame = ffmpeg_get_frame(); + if (frame != NULL) + egl_draw(frame->data); + } + + return DR_OK; +} + +DECODER_RENDERER_CALLBACKS decoder_callbacks_x11 = { + .setup = x11_setup, + .cleanup = x11_cleanup, + .submitDecodeUnit = x11_submit_decode_unit, + .capabilities = CAPABILITY_SLICES_PER_FRAME(4) | CAPABILITY_REFERENCE_FRAME_INVALIDATION | CAPABILITY_DIRECT_SUBMIT, +};