1 module simple_egl;
2 
3 // port of this example:
4 // https://github.com/eyelash/tutorials/blob/master/wayland-egl.c
5 
6 import wayland.client;
7 import wayland.egl;
8 import wayland.cursor;
9 import wayland.util;
10 import wayland.native.util;
11 import zxdg_shell_v6;
12 import derelict.gles.egl;
13 import derelict.gles.gles2;
14 
15 import std.exception;
16 import std.format;
17 import std.stdio;
18 import std.string;
19 import core.time;
20 
21 enum winWidth = 256;
22 enum winHeight = 256;
23 
24 class Display
25 {
26 	WlDisplay display;
27 	WlCompositor compositor;
28 	WlShm shm;
29 	WlSeat seat;
30 	ZxdgShellV6 shell;
31 	WlCursorTheme cursorTheme;
32 	WlCursor defaultCursor;
33 	WlSurface cursorSurf;
34 	WlPointer pointer;
35 	WlKeyboard kbd;
36 
37 	EGLDisplay eglDisplay;
38 	EGLContext eglContext;
39 	EGLConfig config;
40 
41 	EglWindow window;
42 
43 	this()
44 	{
45 		display = enforce(WlDisplay.connect());
46 		auto reg = display.getRegistry();
47 		reg.onGlobal = (WlRegistry reg, uint name, string iface, uint ver)
48 		{
49 			import std.algorithm : min;
50 			if (iface == WlCompositor.iface.name)
51 			{
52 				compositor = cast(WlCompositor)reg.bind(name, WlCompositor.iface, min(ver, 4));
53 			}
54 			else if (iface == WlShm.iface.name)
55 			{
56 				shm = cast(WlShm)reg.bind(name, WlShm.iface, min(ver, 1));
57 				cursorTheme = enforce(
58 					WlCursorTheme.load(null, 32, shm),
59 					"Unable to load default cursor theme"
60 				);
61 				defaultCursor = enforce(
62 					cursorTheme.cursor("left_ptr"),
63 					"Unable to load default left pointer"
64 				);
65 			}
66 			else if (iface == WlSeat.iface.name)
67 			{
68 				seat = cast(WlSeat)reg.bind(name, WlSeat.iface, min(ver, 1));
69 				seat.onCapabilities = &seatCapChanged;
70 
71 			}
72 			else if (iface == ZxdgShellV6.iface.name)
73 			{
74 				shell = cast(ZxdgShellV6)reg.bind(name, ZxdgShellV6.iface, min(ver, 1));
75 				shell.onPing = (ZxdgShellV6 shell, uint serial) {
76 					shell.pong(serial);
77 				};
78 			}
79 		};
80 		display.roundtrip();
81 		reg.destroy();
82 
83 		cursorSurf = compositor.createSurface();
84 
85 		DerelictEGL.load();
86 
87 		eglDisplay = enforce(eglGetDisplay (cast(void*)display.proxy));
88 		enforce(eglInitialize(eglDisplay, null, null) == EGL_TRUE);
89 		enforce(eglBindAPI (EGL_OPENGL_ES_API) == GL_TRUE);
90 
91 
92 		int[] ctxAttribs = [
93 			EGL_CONTEXT_CLIENT_VERSION, 2,
94 			EGL_NONE
95 		];
96 		int[] attributes = [
97 			EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
98 			EGL_RED_SIZE, 8,
99 			EGL_GREEN_SIZE, 8,
100 			EGL_BLUE_SIZE, 8,
101 			EGL_ALPHA_SIZE, 8,
102 			EGL_BUFFER_SIZE, 32,
103 			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
104 			EGL_NONE
105 		];
106 		EGLint numConfig;
107 		enforce(eglChooseConfig (eglDisplay, attributes.ptr, &config, 1, &numConfig) == GL_TRUE);
108 		eglContext = enforce(eglCreateContext (eglDisplay, config, EGL_NO_CONTEXT, ctxAttribs.ptr));
109 	}
110 
111 
112 	void seatCapChanged (WlSeat seat, WlSeat.Capability cap)
113 	{
114 		if ((cap & WlSeat.Capability.pointer) && !pointer)
115 		{
116 			pointer = seat.getPointer();
117 			pointer.onEnter = &pointerEnter;
118 			pointer.onButton = &pointerButton;
119 		}
120 		else if (!(cap & WlSeat.Capability.pointer) && pointer)
121 		{
122 			pointer.destroy();
123 			pointer = null;
124 		}
125 
126 		if ((cap & WlSeat.Capability.keyboard) && !kbd)
127 		{
128 			kbd = seat.getKeyboard();
129 			kbd.onKey = &kbdKey;
130 		}
131 		else if (!(cap & WlSeat.Capability.keyboard) && kbd)
132 		{
133 			kbd.destroy();
134 			kbd = null;
135 		}
136 	}
137 
138 	void pointerEnter(WlPointer pointer, uint serial, WlSurface surface,
139 			WlFixed sx, WlFixed sy)
140 	{
141 		if (defaultCursor)
142 		{
143 			auto img = defaultCursor.images[0];
144 			auto buf = img.buffer;
145 			if (!buf) return;
146 			pointer.setCursor(serial, cursorSurf, img.hotspotX, img.hotspotY);
147 			cursorSurf.attach(buf, 0, 0);
148 			cursorSurf.damage(0, 0, img.width, img.height);
149 			cursorSurf.commit();
150 		}
151 	}
152 
153 	void pointerButton(WlPointer pointer, uint serial, uint time, uint button,
154 			WlPointer.ButtonState state)
155 	{
156 		import linux.input : BTN_LEFT;
157 
158 		if (!window || !window.topLevel) return;
159 
160 		if (button == BTN_LEFT && state == WlPointer.ButtonState.pressed)
161 		{
162 			window.topLevel.move(seat, serial);
163 		}
164 	}
165 
166 	void kbdKey(WlKeyboard keyboard, uint serial, uint time, uint key,
167 			WlKeyboard.KeyState state)
168 	{
169 		import linux.input : KEY_ESC;
170 
171 		if (!window) return;
172 
173 		if (key == KEY_ESC && state) window.running = false;
174 	}
175 
176 	void destroy()
177 	{
178 		eglDestroyContext(eglDisplay, eglContext);
179 		eglTerminate(eglDisplay);
180 		display.disconnect();
181 	}
182 
183 }
184 
185 class EglWindow
186 {
187 	Display dpy;
188 
189 	WlSurface surf;
190 	ZxdgSurfaceV6 xdgSurf;
191 	ZxdgToplevelV6 topLevel;
192 
193 	WlEglWindow eglWin;
194 	EGLSurface eglSurf;
195 
196 	GLuint program;
197 	GLuint vbo;
198 	GLuint posAttrib = 0;
199 	GLuint colAttrib = 1;
200 	GLuint rotationUnif;
201 
202 	MonoTime startTime;
203 	bool running = true;
204 	bool configured;
205 
206 	this (Display dpy)
207 	{
208 		this.dpy = dpy;
209 
210 		surf = enforce(dpy.compositor.createSurface());
211 		xdgSurf = enforce(dpy.shell.getXdgSurface(surf));
212 		topLevel = enforce(xdgSurf.getToplevel());
213 
214 		topLevel.onConfigure = &onTLConfigure;
215 		topLevel.onClose = &onTLClose;
216 		topLevel.setTitle("wayland-d - EGL window");
217 
218 		xdgSurf.onConfigure = (ZxdgSurfaceV6 surf, uint serial)
219 		{
220 			surf.ackConfigure(serial);
221 			configured = true;
222 		};
223 
224 		eglWin = new WlEglWindow(surf, winWidth, winHeight);
225 		eglSurf = enforce(eglCreateWindowSurface(dpy.eglDisplay, dpy.config, cast(void*)eglWin.native, null));
226 
227 		enforce(eglMakeCurrent (dpy.eglDisplay, eglSurf, eglSurf, dpy.eglContext) == GL_TRUE);
228 
229 		DerelictGLES2.load(&loadSymbol);
230 		DerelictGLES2.reload();
231 		writeln("created OpenGLES context: ", fromStringz(glGetString(GL_VERSION)));
232 
233 		initGl();
234 		startTime = MonoTime.currTime();
235 		surf.commit();
236 	}
237 
238 	void onTLConfigure(ZxdgToplevelV6, int width, int height, wl_array* states)
239 	{
240 		if (eglWin) eglWin.resize(winWidth, winHeight, 100, 100);
241 	}
242 
243 	void onTLClose(ZxdgToplevelV6)
244 	{
245 		running = false;
246 	}
247 
248 	GLuint createShader(string source, GLenum stage)
249 	{
250 		const(GLchar)* srcPtr = source.ptr;
251 		auto srcLen = cast(GLint)source.length;
252 		auto sh = glCreateShader(stage);
253 		glShaderSource(sh, 1, &srcPtr, &srcLen);
254 		glCompileShader(sh);
255 		GLint status;
256 		glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
257 		if (status == GL_FALSE)
258 		{
259 			char[1024] log;
260 			GLsizei len;
261 			glGetShaderInfoLog(sh, 1024, &len, log.ptr);
262 			throw new Exception(format(
263 				"%s shader compilation failed:\n%s",
264 				stage == GL_VERTEX_SHADER ? "vertex" : "fragment",
265 				log[0 .. len].idup));
266 		}
267 
268 		return sh;
269 	}
270 
271 	GLuint buildProgram()
272 	{
273 		immutable vertSrc = "
274 			uniform mat4 rotation;
275 			attribute vec4 pos;
276 			attribute vec4 col;
277 			varying vec4 v_col;
278 			void main() {
279 				gl_Position = rotation * pos;
280 				v_col = col;
281 			}
282 		";
283 		immutable fragSrc = "
284 			precision mediump float;
285 			varying vec4 v_col;
286 			void main() {
287 				gl_FragColor = v_col;
288 			}
289 		";
290 		auto vertSh = createShader(vertSrc, GL_VERTEX_SHADER);
291 		auto fragSh = createShader(fragSrc, GL_FRAGMENT_SHADER);
292 
293 		GLuint program = glCreateProgram();
294 		glAttachShader(program, vertSh);
295 		glAttachShader(program, fragSh);
296 		glLinkProgram(program);
297 		GLint status;
298 		glGetProgramiv(program, GL_LINK_STATUS, &status);
299 		enforce(status);
300 
301 		glDeleteShader(vertSh);
302 		glDeleteShader(fragSh);
303 
304 		return program;
305 	}
306 
307 	void initGl()
308 	{
309 		program = buildProgram();
310 
311 		glGenBuffers(1, &vbo);
312 		glBindBuffer(GL_ARRAY_BUFFER, vbo);
313 
314 		immutable float[] colorTriangle = [
315 			// pos
316 			0f, 0.8f, 0f, 1f,
317 			-0.7f, -0.6f, 0f, 1f,
318 			0.7f, -0.6f, 0f, 1f,
319 			// col
320 			1f, 0.3f, 0.3f, 0.5f,
321 			0.3f, 1f, 0.3f, 0.5f,
322 			0.3f, 0.3f, 1f, 0.5f,
323 		];
324 
325 		glBufferData(GL_ARRAY_BUFFER,
326 			colorTriangle.length*4,
327 			cast(const(void*))colorTriangle.ptr,
328 			GL_STATIC_DRAW);
329 
330 		glUseProgram(program);
331 		glBindAttribLocation(program, posAttrib, "pos");
332 		glBindAttribLocation(program, colAttrib, "col");
333 		rotationUnif = glGetUniformLocation(program, "rotation");
334 
335 		glBindBuffer(GL_ARRAY_BUFFER, 0);
336 	}
337 
338 	void draw()
339 	{
340 		import std.math : sin, cos, PI;
341 
342 		glViewport(0, 0, winWidth, winHeight);
343 		glClearColor (0.15f, 0.15f, 0.15f, 0.5f);
344 		glClear (GL_COLOR_BUFFER_BIT);
345 
346 		immutable speedDiv = 5f;
347 		immutable msecs = (MonoTime.currTime - startTime).total!"msecs";
348 		immutable angle = ((msecs / speedDiv) % 360) * PI / 180f;
349 
350 		immutable s = sin(angle);
351 		immutable c = cos(angle);
352 		immutable float[16] mat = [
353 			c, 0, s, 0,
354 			0, 1, 0, 0,
355 			-s, 0, c, 0,
356 			0, 0, 0, 1,
357 		];
358 
359 		glUniformMatrix4fv(rotationUnif, 1, GL_FALSE, mat.ptr);
360 		glBindBuffer(GL_ARRAY_BUFFER, vbo);
361 		glVertexAttribPointer(posAttrib, 4, GL_FLOAT, GL_FALSE, 0, null);
362 		glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, 0, cast(void*)(3*4*4));
363 		glEnableVertexAttribArray(posAttrib);
364 		glEnableVertexAttribArray(colAttrib);
365 
366 		glDrawArrays(GL_TRIANGLES, 0, 3);
367 
368 		glDisableVertexAttribArray(colAttrib);
369 		glDisableVertexAttribArray(posAttrib);
370 		glBindBuffer(GL_ARRAY_BUFFER, 0);
371 
372 		eglSwapBuffers (dpy.eglDisplay, eglSurf);
373 	}
374 
375 	void destroy()
376 	{
377 		glUseProgram(0);
378 		glDeleteProgram(program);
379 		glDeleteBuffers(1, &vbo);
380 		eglDestroySurface(dpy.eglDisplay, eglSurf);
381 		eglWin.destroy();
382 		topLevel.destroy();
383 		xdgSurf.destroy();
384 		surf.destroy();
385 	}
386 }
387 
388 void* loadSymbol(string name)
389 {
390     import std.format : format;
391 	import std.string : toStringz;
392 
393     auto sym = enforce (
394 		eglGetProcAddress(toStringz(name)),
395     	format("Failed to load symbol %s: 0x%x", name, eglGetError())
396 	);
397     return sym;
398 }
399 
400 int main ()
401 {
402 	version(WlDynamic)
403 	{
404 		wlClientDynLib.load();
405 		wlEglDynLib.load();
406 		wlCursorDynLib.load();
407 	}
408 
409 	auto dpy = new Display();
410 	scope(exit) dpy.destroy();
411 	auto win = new EglWindow(dpy);
412 	scope(exit) win.destroy();
413 
414 	dpy.window = win;
415 
416 	while(win.running)
417 	{
418 		if (win.configured) dpy.display.dispatch();
419 		else dpy.display.dispatchPending();
420 		win.draw();
421 	}
422 
423 	return 0;
424 }