blob: dff6d2d64e7a7eef10a14e459a1ac87d5ea53b6a [file] [log] [blame]
// 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;
}