| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build linux,!android openbsd |
| |
| #include "_cgo_export.h" |
| #include <EGL/egl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| Atom net_wm_name; |
| Atom utf8_string; |
| Atom wm_delete_window; |
| Atom wm_protocols; |
| Atom wm_take_focus; |
| |
| EGLConfig e_config; |
| EGLContext e_ctx; |
| EGLDisplay e_dpy; |
| Colormap x_colormap; |
| Display *x_dpy; |
| XVisualInfo *x_visual_info; |
| Window x_root; |
| |
| // TODO: share code with eglErrString |
| char * |
| eglGetErrorStr() { |
| switch (eglGetError()) { |
| case EGL_SUCCESS: |
| return "EGL_SUCCESS"; |
| case EGL_NOT_INITIALIZED: |
| return "EGL_NOT_INITIALIZED"; |
| case EGL_BAD_ACCESS: |
| return "EGL_BAD_ACCESS"; |
| case EGL_BAD_ALLOC: |
| return "EGL_BAD_ALLOC"; |
| case EGL_BAD_ATTRIBUTE: |
| return "EGL_BAD_ATTRIBUTE"; |
| case EGL_BAD_CONFIG: |
| return "EGL_BAD_CONFIG"; |
| case EGL_BAD_CONTEXT: |
| return "EGL_BAD_CONTEXT"; |
| case EGL_BAD_CURRENT_SURFACE: |
| return "EGL_BAD_CURRENT_SURFACE"; |
| case EGL_BAD_DISPLAY: |
| return "EGL_BAD_DISPLAY"; |
| case EGL_BAD_MATCH: |
| return "EGL_BAD_MATCH"; |
| case EGL_BAD_NATIVE_PIXMAP: |
| return "EGL_BAD_NATIVE_PIXMAP"; |
| case EGL_BAD_NATIVE_WINDOW: |
| return "EGL_BAD_NATIVE_WINDOW"; |
| case EGL_BAD_PARAMETER: |
| return "EGL_BAD_PARAMETER"; |
| case EGL_BAD_SURFACE: |
| return "EGL_BAD_SURFACE"; |
| case EGL_CONTEXT_LOST: |
| return "EGL_CONTEXT_LOST"; |
| } |
| return "unknown EGL error"; |
| } |
| |
| void |
| startDriver() { |
| x_dpy = XOpenDisplay(NULL); |
| if (!x_dpy) { |
| fprintf(stderr, "XOpenDisplay failed\n"); |
| exit(1); |
| } |
| e_dpy = eglGetDisplay(x_dpy); |
| if (!e_dpy) { |
| fprintf(stderr, "eglGetDisplay failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| EGLint e_major, e_minor; |
| if (!eglInitialize(e_dpy, &e_major, &e_minor)) { |
| fprintf(stderr, "eglInitialize failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| if (!eglBindAPI(EGL_OPENGL_ES_API)) { |
| fprintf(stderr, "eglBindAPI failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| |
| static const EGLint attribs[] = { |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
| EGL_BLUE_SIZE, 8, |
| EGL_GREEN_SIZE, 8, |
| EGL_RED_SIZE, 8, |
| EGL_DEPTH_SIZE, 16, |
| EGL_CONFIG_CAVEAT, EGL_NONE, |
| EGL_NONE |
| }; |
| EGLint num_configs; |
| if (!eglChooseConfig(e_dpy, attribs, &e_config, 1, &num_configs)) { |
| fprintf(stderr, "eglChooseConfig failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| EGLint vid; |
| if (!eglGetConfigAttrib(e_dpy, e_config, EGL_NATIVE_VISUAL_ID, &vid)) { |
| fprintf(stderr, "eglGetConfigAttrib failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| |
| XVisualInfo visTemplate; |
| visTemplate.visualid = vid; |
| int num_visuals; |
| x_visual_info = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals); |
| if (!x_visual_info) { |
| fprintf(stderr, "XGetVisualInfo failed\n"); |
| exit(1); |
| } |
| |
| x_root = RootWindow(x_dpy, DefaultScreen(x_dpy)); |
| x_colormap = XCreateColormap(x_dpy, x_root, x_visual_info->visual, AllocNone); |
| if (!x_colormap) { |
| fprintf(stderr, "XCreateColormap failed\n"); |
| exit(1); |
| } |
| |
| static const EGLint ctx_attribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 3, |
| EGL_NONE |
| }; |
| e_ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs); |
| if (!e_ctx) { |
| fprintf(stderr, "eglCreateContext failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| |
| net_wm_name = XInternAtom(x_dpy, "_NET_WM_NAME", False); |
| utf8_string = XInternAtom(x_dpy, "UTF8_STRING", False); |
| wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", False); |
| wm_protocols = XInternAtom(x_dpy, "WM_PROTOCOLS", False); |
| wm_take_focus = XInternAtom(x_dpy, "WM_TAKE_FOCUS", False); |
| |
| const int key_lo = 8; |
| const int key_hi = 255; |
| int keysyms_per_keycode; |
| KeySym *keysyms = XGetKeyboardMapping(x_dpy, key_lo, key_hi-key_lo+1, &keysyms_per_keycode); |
| if (keysyms_per_keycode < 2) { |
| fprintf(stderr, "XGetKeyboardMapping returned too few keysyms per keycode: %d\n", keysyms_per_keycode); |
| exit(1); |
| } |
| int k; |
| for (k = key_lo; k <= key_hi; k++) { |
| onKeysym(k, |
| keysyms[(k-key_lo)*keysyms_per_keycode + 0], |
| keysyms[(k-key_lo)*keysyms_per_keycode + 1]); |
| } |
| //TODO: use GetModifierMapping to figure out which modifier is the numlock modifier. |
| } |
| |
| void |
| processEvents() { |
| while (XPending(x_dpy)) { |
| XEvent ev; |
| XNextEvent(x_dpy, &ev); |
| switch (ev.type) { |
| case KeyPress: |
| case KeyRelease: |
| onKey(ev.xkey.window, ev.xkey.state, ev.xkey.keycode, ev.type == KeyPress ? 1 : 2); |
| break; |
| case ButtonPress: |
| case ButtonRelease: |
| onMouse(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y, ev.xbutton.state, ev.xbutton.button, |
| ev.type == ButtonPress ? 1 : 2); |
| break; |
| case MotionNotify: |
| onMouse(ev.xmotion.window, ev.xmotion.x, ev.xmotion.y, ev.xmotion.state, 0, 0); |
| break; |
| case FocusIn: |
| case FocusOut: |
| onFocus(ev.xmotion.window, ev.type == FocusIn); |
| break; |
| case Expose: |
| // A non-zero Count means that there are more expose events coming. For |
| // example, a non-rectangular exposure (e.g. from a partially overlapped |
| // window) will result in multiple expose events whose dirty rectangles |
| // combine to define the dirty region. Go's paint events do not provide |
| // dirty regions, so we only pass on the final X11 expose event. |
| if (ev.xexpose.count == 0) { |
| onExpose(ev.xexpose.window); |
| } |
| break; |
| case ConfigureNotify: |
| onConfigure(ev.xconfigure.window, ev.xconfigure.x, ev.xconfigure.y, |
| ev.xconfigure.width, ev.xconfigure.height, |
| DisplayWidth(x_dpy, DefaultScreen(x_dpy)), |
| DisplayWidthMM(x_dpy, DefaultScreen(x_dpy))); |
| break; |
| case ClientMessage: |
| if ((ev.xclient.message_type != wm_protocols) || (ev.xclient.format != 32)) { |
| break; |
| } |
| Atom a = ev.xclient.data.l[0]; |
| if (a == wm_delete_window) { |
| onDeleteWindow(ev.xclient.window); |
| } else if (a == wm_take_focus) { |
| XSetInputFocus(x_dpy, ev.xclient.window, RevertToParent, ev.xclient.data.l[1]); |
| } |
| break; |
| } |
| } |
| } |
| |
| void |
| makeCurrent(uintptr_t surface) { |
| EGLSurface surf = (EGLSurface)(surface); |
| if (!eglMakeCurrent(e_dpy, surf, surf, e_ctx)) { |
| fprintf(stderr, "eglMakeCurrent failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| } |
| |
| void |
| swapBuffers(uintptr_t surface) { |
| EGLSurface surf = (EGLSurface)(surface); |
| if (!eglSwapBuffers(e_dpy, surf)) { |
| fprintf(stderr, "eglSwapBuffers failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| } |
| |
| void |
| doCloseWindow(uintptr_t id) { |
| Window win = (Window)(id); |
| XDestroyWindow(x_dpy, win); |
| } |
| |
| uintptr_t |
| doNewWindow(int width, int height, char* title, int title_len) { |
| XSetWindowAttributes attr; |
| attr.colormap = x_colormap; |
| attr.event_mask = |
| KeyPressMask | |
| KeyReleaseMask | |
| ButtonPressMask | |
| ButtonReleaseMask | |
| PointerMotionMask | |
| ExposureMask | |
| StructureNotifyMask | |
| FocusChangeMask; |
| |
| Window win = XCreateWindow( |
| x_dpy, x_root, 0, 0, width, height, 0, x_visual_info->depth, InputOutput, |
| x_visual_info->visual, CWColormap | CWEventMask, &attr); |
| |
| XSizeHints sizehints; |
| sizehints.width = width; |
| sizehints.height = height; |
| sizehints.flags = USSize; |
| XSetNormalHints(x_dpy, win, &sizehints); |
| |
| Atom atoms[2]; |
| atoms[0] = wm_delete_window; |
| atoms[1] = wm_take_focus; |
| XSetWMProtocols(x_dpy, win, atoms, 2); |
| |
| XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints); |
| XChangeProperty(x_dpy, win, net_wm_name, utf8_string, 8, PropModeReplace, title, title_len); |
| |
| return win; |
| } |
| |
| uintptr_t |
| doShowWindow(uintptr_t id) { |
| Window win = (Window)(id); |
| XMapWindow(x_dpy, win); |
| EGLSurface surf = eglCreateWindowSurface(e_dpy, e_config, win, NULL); |
| if (!surf) { |
| fprintf(stderr, "eglCreateWindowSurface failed: %s\n", eglGetErrorStr()); |
| exit(1); |
| } |
| return (uintptr_t)(surf); |
| } |
| |
| uintptr_t |
| surfaceCreate() { |
| static const EGLint ctx_attribs[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 3, |
| EGL_NONE |
| }; |
| EGLContext ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs); |
| if (!ctx) { |
| fprintf(stderr, "surface eglCreateContext failed: %s\n", eglGetErrorStr()); |
| return 0; |
| } |
| |
| static const EGLint cfg_attribs[] = { |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, |
| EGL_BLUE_SIZE, 8, |
| EGL_GREEN_SIZE, 8, |
| EGL_RED_SIZE, 8, |
| EGL_DEPTH_SIZE, 16, |
| EGL_CONFIG_CAVEAT, EGL_NONE, |
| EGL_NONE |
| }; |
| EGLConfig cfg; |
| EGLint num_configs; |
| if (!eglChooseConfig(e_dpy, cfg_attribs, &cfg, 1, &num_configs)) { |
| fprintf(stderr, "gldriver: surface eglChooseConfig failed: %s\n", eglGetErrorStr()); |
| return 0; |
| } |
| |
| // TODO: use the size of the monitor as a bound for texture size. |
| static const EGLint attribs[] = { |
| EGL_WIDTH, 4096, |
| EGL_HEIGHT, 3072, |
| EGL_NONE |
| }; |
| EGLSurface surface = eglCreatePbufferSurface(e_dpy, cfg, attribs); |
| if (!surface) { |
| fprintf(stderr, "gldriver: surface eglCreatePbufferSurface failed: %s\n", eglGetErrorStr()); |
| return 0; |
| } |
| |
| if (!eglMakeCurrent(e_dpy, surface, surface, ctx)) { |
| fprintf(stderr, "gldriver: surface eglMakeCurrent failed: %s\n", eglGetErrorStr()); |
| return 0; |
| } |
| |
| return (uintptr_t)surface; |
| } |