1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 
5 // +build linux,!android openbsd
6 
7 #include "_cgo_export.h"
8 #include <EGL/egl.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 Atom net_wm_name;
14 Atom utf8_string;
15 Atom wm_delete_window;
16 Atom wm_protocols;
17 Atom wm_take_focus;
18 
19 EGLConfig e_config;
20 EGLContext e_ctx;
21 EGLDisplay e_dpy;
22 Colormap x_colormap;
23 Display *x_dpy;
24 XVisualInfo *x_visual_info;
25 Window x_root;
26 
27 // TODO: share code with eglErrString
28 char *
eglGetErrorStr()29 eglGetErrorStr() {
30 	switch (eglGetError()) {
31 	case EGL_SUCCESS:
32 		return "EGL_SUCCESS";
33 	case EGL_NOT_INITIALIZED:
34 		return "EGL_NOT_INITIALIZED";
35 	case EGL_BAD_ACCESS:
36 		return "EGL_BAD_ACCESS";
37 	case EGL_BAD_ALLOC:
38 		return "EGL_BAD_ALLOC";
39 	case EGL_BAD_ATTRIBUTE:
40 		return "EGL_BAD_ATTRIBUTE";
41 	case EGL_BAD_CONFIG:
42 		return "EGL_BAD_CONFIG";
43 	case EGL_BAD_CONTEXT:
44 		return "EGL_BAD_CONTEXT";
45 	case EGL_BAD_CURRENT_SURFACE:
46 		return "EGL_BAD_CURRENT_SURFACE";
47 	case EGL_BAD_DISPLAY:
48 		return "EGL_BAD_DISPLAY";
49 	case EGL_BAD_MATCH:
50 		return "EGL_BAD_MATCH";
51 	case EGL_BAD_NATIVE_PIXMAP:
52 		return "EGL_BAD_NATIVE_PIXMAP";
53 	case EGL_BAD_NATIVE_WINDOW:
54 		return "EGL_BAD_NATIVE_WINDOW";
55 	case EGL_BAD_PARAMETER:
56 		return "EGL_BAD_PARAMETER";
57 	case EGL_BAD_SURFACE:
58 		return "EGL_BAD_SURFACE";
59 	case EGL_CONTEXT_LOST:
60 		return "EGL_CONTEXT_LOST";
61 	}
62 	return "unknown EGL error";
63 }
64 
65 void
startDriver()66 startDriver() {
67 	x_dpy = XOpenDisplay(NULL);
68 	if (!x_dpy) {
69 		fprintf(stderr, "XOpenDisplay failed\n");
70 		exit(1);
71 	}
72 	e_dpy = eglGetDisplay(x_dpy);
73 	if (!e_dpy) {
74 		fprintf(stderr, "eglGetDisplay failed: %s\n", eglGetErrorStr());
75 		exit(1);
76 	}
77 	EGLint e_major, e_minor;
78 	if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
79 		fprintf(stderr, "eglInitialize failed: %s\n", eglGetErrorStr());
80 		exit(1);
81 	}
82 	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
83 		fprintf(stderr, "eglBindAPI failed: %s\n", eglGetErrorStr());
84 		exit(1);
85 	}
86 
87 	static const EGLint attribs[] = {
88 		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
89 		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
90 		EGL_BLUE_SIZE, 8,
91 		EGL_GREEN_SIZE, 8,
92 		EGL_RED_SIZE, 8,
93 		EGL_DEPTH_SIZE, 16,
94 		EGL_CONFIG_CAVEAT, EGL_NONE,
95 		EGL_NONE
96 	};
97 	EGLint num_configs;
98 	if (!eglChooseConfig(e_dpy, attribs, &e_config, 1, &num_configs)) {
99 		fprintf(stderr, "eglChooseConfig failed: %s\n", eglGetErrorStr());
100 		exit(1);
101 	}
102 	EGLint vid;
103 	if (!eglGetConfigAttrib(e_dpy, e_config, EGL_NATIVE_VISUAL_ID, &vid)) {
104 		fprintf(stderr, "eglGetConfigAttrib failed: %s\n", eglGetErrorStr());
105 		exit(1);
106 	}
107 
108 	XVisualInfo visTemplate;
109 	visTemplate.visualid = vid;
110 	int num_visuals;
111 	x_visual_info = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals);
112 	if (!x_visual_info) {
113 		fprintf(stderr, "XGetVisualInfo failed\n");
114 		exit(1);
115 	}
116 
117 	x_root = RootWindow(x_dpy, DefaultScreen(x_dpy));
118 	x_colormap = XCreateColormap(x_dpy, x_root, x_visual_info->visual, AllocNone);
119 	if (!x_colormap) {
120 		fprintf(stderr, "XCreateColormap failed\n");
121 		exit(1);
122 	}
123 
124 	static const EGLint ctx_attribs[] = {
125 		EGL_CONTEXT_CLIENT_VERSION, 3,
126 		EGL_NONE
127 	};
128 	e_ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
129 	if (!e_ctx) {
130 		fprintf(stderr, "eglCreateContext failed: %s\n", eglGetErrorStr());
131 		exit(1);
132 	}
133 
134 	net_wm_name = XInternAtom(x_dpy, "_NET_WM_NAME", False);
135 	utf8_string = XInternAtom(x_dpy, "UTF8_STRING", False);
136 	wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", False);
137 	wm_protocols = XInternAtom(x_dpy, "WM_PROTOCOLS", False);
138 	wm_take_focus = XInternAtom(x_dpy, "WM_TAKE_FOCUS", False);
139 
140 	const int key_lo = 8;
141 	const int key_hi = 255;
142 	int keysyms_per_keycode;
143 	KeySym *keysyms = XGetKeyboardMapping(x_dpy, key_lo, key_hi-key_lo+1, &keysyms_per_keycode);
144 	if (keysyms_per_keycode < 2) {
145 		fprintf(stderr, "XGetKeyboardMapping returned too few keysyms per keycode: %d\n", keysyms_per_keycode);
146 		exit(1);
147 	}
148 	int k;
149 	for (k = key_lo; k <= key_hi; k++) {
150 		onKeysym(k,
151 			keysyms[(k-key_lo)*keysyms_per_keycode + 0],
152 			keysyms[(k-key_lo)*keysyms_per_keycode + 1]);
153 	}
154 	//TODO: use GetModifierMapping to figure out which modifier is the numlock modifier.
155 }
156 
157 void
processEvents()158 processEvents() {
159 	while (XPending(x_dpy)) {
160 		XEvent ev;
161 		XNextEvent(x_dpy, &ev);
162 		switch (ev.type) {
163 		case KeyPress:
164 		case KeyRelease:
165 			onKey(ev.xkey.window, ev.xkey.state, ev.xkey.keycode, ev.type == KeyPress ? 1 : 2);
166 			break;
167 		case ButtonPress:
168 		case ButtonRelease:
169 			onMouse(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y, ev.xbutton.state, ev.xbutton.button,
170 				ev.type == ButtonPress ? 1 : 2);
171 			break;
172 		case MotionNotify:
173 			onMouse(ev.xmotion.window, ev.xmotion.x, ev.xmotion.y, ev.xmotion.state, 0, 0);
174 			break;
175 		case FocusIn:
176 		case FocusOut:
177 			onFocus(ev.xmotion.window, ev.type == FocusIn);
178 			break;
179 		case Expose:
180 			// A non-zero Count means that there are more expose events coming. For
181 			// example, a non-rectangular exposure (e.g. from a partially overlapped
182 			// window) will result in multiple expose events whose dirty rectangles
183 			// combine to define the dirty region. Go's paint events do not provide
184 			// dirty regions, so we only pass on the final X11 expose event.
185 			if (ev.xexpose.count == 0) {
186 				onExpose(ev.xexpose.window);
187 			}
188 			break;
189 		case ConfigureNotify:
190 			onConfigure(ev.xconfigure.window, ev.xconfigure.x, ev.xconfigure.y,
191 				ev.xconfigure.width, ev.xconfigure.height,
192 				DisplayWidth(x_dpy, DefaultScreen(x_dpy)),
193 				DisplayWidthMM(x_dpy, DefaultScreen(x_dpy)));
194 			break;
195 		case ClientMessage:
196 			if ((ev.xclient.message_type != wm_protocols) || (ev.xclient.format != 32)) {
197 				break;
198 			}
199 			Atom a = ev.xclient.data.l[0];
200 			if (a == wm_delete_window) {
201 				onDeleteWindow(ev.xclient.window);
202 			} else if (a == wm_take_focus) {
203 				XSetInputFocus(x_dpy, ev.xclient.window, RevertToParent, ev.xclient.data.l[1]);
204 			}
205 			break;
206 		}
207 	}
208 }
209 
210 void
makeCurrent(uintptr_t surface)211 makeCurrent(uintptr_t surface) {
212 	EGLSurface surf = (EGLSurface)(surface);
213 	if (!eglMakeCurrent(e_dpy, surf, surf, e_ctx)) {
214 		fprintf(stderr, "eglMakeCurrent failed: %s\n", eglGetErrorStr());
215 		exit(1);
216 	}
217 }
218 
219 void
swapBuffers(uintptr_t surface)220 swapBuffers(uintptr_t surface) {
221 	EGLSurface surf = (EGLSurface)(surface);
222 	if (!eglSwapBuffers(e_dpy, surf)) {
223 		fprintf(stderr, "eglSwapBuffers failed: %s\n", eglGetErrorStr());
224 		exit(1);
225 	}
226 }
227 
228 void
doCloseWindow(uintptr_t id)229 doCloseWindow(uintptr_t id) {
230 	Window win = (Window)(id);
231 	XDestroyWindow(x_dpy, win);
232 }
233 
234 uintptr_t
doNewWindow(int width,int height,char * title,int title_len)235 doNewWindow(int width, int height, char* title, int title_len) {
236 	XSetWindowAttributes attr;
237 	attr.colormap = x_colormap;
238 	attr.event_mask =
239 		KeyPressMask |
240 		KeyReleaseMask |
241 		ButtonPressMask |
242 		ButtonReleaseMask |
243 		PointerMotionMask |
244 		ExposureMask |
245 		StructureNotifyMask |
246 		FocusChangeMask;
247 
248 	Window win = XCreateWindow(
249 		x_dpy, x_root, 0, 0, width, height, 0, x_visual_info->depth, InputOutput,
250 		x_visual_info->visual, CWColormap | CWEventMask, &attr);
251 
252 	XSizeHints sizehints;
253 	sizehints.width = width;
254 	sizehints.height = height;
255 	sizehints.flags = USSize;
256 	XSetNormalHints(x_dpy, win, &sizehints);
257 
258 	Atom atoms[2];
259 	atoms[0] = wm_delete_window;
260 	atoms[1] = wm_take_focus;
261 	XSetWMProtocols(x_dpy, win, atoms, 2);
262 
263 	XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints);
264 	XChangeProperty(x_dpy, win, net_wm_name, utf8_string, 8, PropModeReplace, title, title_len);
265 
266 	return win;
267 }
268 
269 uintptr_t
doShowWindow(uintptr_t id)270 doShowWindow(uintptr_t id) {
271 	Window win = (Window)(id);
272 	XMapWindow(x_dpy, win);
273 	EGLSurface surf = eglCreateWindowSurface(e_dpy, e_config, win, NULL);
274 	if (!surf) {
275 		fprintf(stderr, "eglCreateWindowSurface failed: %s\n", eglGetErrorStr());
276 		exit(1);
277 	}
278 	return (uintptr_t)(surf);
279 }
280 
281 uintptr_t
surfaceCreate()282 surfaceCreate() {
283 	static const EGLint ctx_attribs[] = {
284 		EGL_CONTEXT_CLIENT_VERSION, 3,
285 		EGL_NONE
286 	};
287 	EGLContext ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
288 	if (!ctx) {
289 		fprintf(stderr, "surface eglCreateContext failed: %s\n", eglGetErrorStr());
290 		return 0;
291 	}
292 
293 	static const EGLint cfg_attribs[] = {
294 		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
295 		EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
296 		EGL_BLUE_SIZE, 8,
297 		EGL_GREEN_SIZE, 8,
298 		EGL_RED_SIZE, 8,
299 		EGL_DEPTH_SIZE, 16,
300 		EGL_CONFIG_CAVEAT, EGL_NONE,
301 		EGL_NONE
302 	};
303 	EGLConfig cfg;
304 	EGLint num_configs;
305 	if (!eglChooseConfig(e_dpy, cfg_attribs, &cfg, 1, &num_configs)) {
306 		fprintf(stderr, "gldriver: surface eglChooseConfig failed: %s\n", eglGetErrorStr());
307 		return 0;
308 	}
309 
310 	// TODO: use the size of the monitor as a bound for texture size.
311 	static const EGLint attribs[] = {
312 		EGL_WIDTH, 4096,
313 		EGL_HEIGHT, 3072,
314 		EGL_NONE
315 	};
316 	EGLSurface surface = eglCreatePbufferSurface(e_dpy, cfg, attribs);
317 	if (!surface) {
318 		fprintf(stderr, "gldriver: surface eglCreatePbufferSurface failed: %s\n", eglGetErrorStr());
319 		return 0;
320 	}
321 
322 	if (!eglMakeCurrent(e_dpy, surface, surface, ctx)) {
323 		fprintf(stderr, "gldriver: surface eglMakeCurrent failed: %s\n", eglGetErrorStr());
324 		return 0;
325 	}
326 
327 	return (uintptr_t)surface;
328 }
329