1 /*
2 * Copyright (C) 2016 Google, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <cassert>
18 #include <dlfcn.h>
19 #include <sstream>
20 #include <time.h>
21
22 #include "Game.h"
23 #include "Helpers.h"
24 #include "ShellWayland.h"
25 #include <stdio.h>
26 #include <string.h>
27 #include <linux/input.h>
28
29 /* Unused attribute / variable MACRO.
30 Some methods of classes' heirs do not need all fuction parameters.
31 This triggers warning on GCC platfoms. This macro will silence them.
32 */
33 #if defined(__GNUC__)
34 #define UNUSED __attribute__((unused))
35 #else
36 #define UNUSED
37 #endif
38
39 namespace {
40
41 class PosixTimer {
42 public:
PosixTimer()43 PosixTimer() { reset(); }
44
reset()45 void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); }
46
get() const47 double get() const {
48 struct timespec now;
49 clock_gettime(CLOCK_MONOTONIC, &now);
50
51 constexpr long one_s_in_ns = 1000 * 1000 * 1000;
52 constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns);
53
54 time_t s = now.tv_sec - start_.tv_sec;
55 long ns;
56 if (now.tv_nsec > start_.tv_nsec) {
57 ns = now.tv_nsec - start_.tv_nsec;
58 } else {
59 assert(s > 0);
60 s--;
61 ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec);
62 }
63
64 return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d;
65 }
66
67 private:
68 struct timespec start_;
69 };
70
71 } // namespace
72
handle_ping(void * data,wl_shell_surface * shell_surface,uint32_t serial)73 void ShellWayland::handle_ping(void *data, wl_shell_surface *shell_surface, uint32_t serial) {
74 wl_shell_surface_pong(shell_surface, serial);
75 }
76
handle_configure(void * data,wl_shell_surface * shell_surface,uint32_t edges,int32_t width,int32_t height)77 void ShellWayland::handle_configure(void *data, wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) {}
78
handle_popup_done(void * data,wl_shell_surface * shell_surface)79 void ShellWayland::handle_popup_done(void *data, wl_shell_surface *shell_surface) {}
80
81 const wl_shell_surface_listener ShellWayland::shell_surface_listener = {handle_ping, handle_configure, handle_popup_done};
82
pointer_handle_enter(void * data,struct wl_pointer * pointer,uint32_t serial,struct wl_surface * surface,wl_fixed_t sx,wl_fixed_t sy)83 void ShellWayland::pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface,
84 wl_fixed_t sx, wl_fixed_t sy) {}
85
pointer_handle_leave(void * data,struct wl_pointer * pointer,uint32_t serial,struct wl_surface * surface)86 void ShellWayland::pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {}
87
pointer_handle_motion(void * data,struct wl_pointer * pointer,uint32_t time,wl_fixed_t sx,wl_fixed_t sy)88 void ShellWayland::pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {}
89
pointer_handle_button(void * data,struct wl_pointer * wl_pointer,uint32_t serial,uint32_t time,uint32_t button,uint32_t state)90 void ShellWayland::pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button,
91 uint32_t state) {
92 if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) {
93 ShellWayland *shell = (ShellWayland *)data;
94 wl_shell_surface_move(shell->shell_surface_, shell->seat_, serial);
95 }
96 }
97
pointer_handle_axis(void * data,struct wl_pointer * wl_pointer,uint32_t time,uint32_t axis,wl_fixed_t value)98 void ShellWayland::pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {}
99
100 const wl_pointer_listener ShellWayland::pointer_listener = {
101 pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis,
102 };
103
keyboard_handle_keymap(void * data,struct wl_keyboard * keyboard,uint32_t format,int fd,uint32_t size)104 void ShellWayland::keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {}
105
keyboard_handle_enter(void * data,struct wl_keyboard * keyboard,uint32_t serial,struct wl_surface * surface,struct wl_array * keys)106 void ShellWayland::keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface,
107 struct wl_array *keys) {}
108
keyboard_handle_leave(void * data,struct wl_keyboard * keyboard,uint32_t serial,struct wl_surface * surface)109 void ShellWayland::keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {}
110
keyboard_handle_key(void * data,struct wl_keyboard * keyboard,uint32_t serial,uint32_t time,uint32_t key,uint32_t state)111 void ShellWayland::keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key,
112 uint32_t state) {
113 if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return;
114 ShellWayland *shell = (ShellWayland *)data;
115 Game::Key game_key;
116 switch (key) {
117 case KEY_ESC: // Escape
118 #undef KEY_ESC
119 game_key = Game::KEY_ESC;
120 break;
121 case KEY_UP: // up arrow key
122 #undef KEY_UP
123 game_key = Game::KEY_UP;
124 break;
125 case KEY_DOWN: // right arrow key
126 #undef KEY_DOWN
127 game_key = Game::KEY_DOWN;
128 break;
129 case KEY_SPACE: // space bar
130 #undef KEY_SPACE
131 game_key = Game::KEY_SPACE;
132 break;
133 default:
134 #undef KEY_UNKNOWN
135 game_key = Game::KEY_UNKNOWN;
136 break;
137 }
138 shell->game_.on_key(game_key);
139 }
140
keyboard_handle_modifiers(void * data,wl_keyboard * keyboard,uint32_t serial,uint32_t mods_depressed,uint32_t mods_latched,uint32_t mods_locked,uint32_t group)141 void ShellWayland::keyboard_handle_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed,
142 uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {}
143
144 const wl_keyboard_listener ShellWayland::keyboard_listener = {
145 keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers,
146 };
147
seat_handle_capabilities(void * data,wl_seat * seat,uint32_t caps)148 void ShellWayland::seat_handle_capabilities(void *data, wl_seat *seat, uint32_t caps) {
149 // Subscribe to pointer events
150 ShellWayland *shell = (ShellWayland *)data;
151 if ((caps & WL_SEAT_CAPABILITY_POINTER) && !shell->pointer_) {
152 shell->pointer_ = wl_seat_get_pointer(seat);
153 wl_pointer_add_listener(shell->pointer_, &pointer_listener, shell);
154 } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && shell->pointer_) {
155 wl_pointer_destroy(shell->pointer_);
156 shell->pointer_ = NULL;
157 }
158 // Subscribe to keyboard events
159 if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
160 shell->keyboard_ = wl_seat_get_keyboard(seat);
161 wl_keyboard_add_listener(shell->keyboard_, &keyboard_listener, shell);
162 } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
163 wl_keyboard_destroy(shell->keyboard_);
164 shell->keyboard_ = NULL;
165 }
166 }
167
168 const wl_seat_listener ShellWayland::seat_listener = {
169 seat_handle_capabilities,
170 };
171
registry_handle_global(void * data,wl_registry * registry,uint32_t id,const char * interface,uint32_t version)172 void ShellWayland::registry_handle_global(void *data, wl_registry *registry, uint32_t id, const char *interface, uint32_t version) {
173 // pickup wayland objects when they appear
174 ShellWayland *shell = (ShellWayland *)data;
175 if (strcmp(interface, "wl_compositor") == 0) {
176 shell->compositor_ = (wl_compositor *)wl_registry_bind(registry, id, &wl_compositor_interface, 1);
177 } else if (strcmp(interface, "wl_shell") == 0) {
178 shell->shell_ = (wl_shell *)wl_registry_bind(registry, id, &wl_shell_interface, 1);
179 } else if (strcmp(interface, "wl_seat") == 0) {
180 shell->seat_ = (wl_seat *)wl_registry_bind(registry, id, &wl_seat_interface, 1);
181 wl_seat_add_listener(shell->seat_, &seat_listener, shell);
182 }
183 }
184
registry_handle_global_remove(void * data,wl_registry * registry,uint32_t name)185 void ShellWayland::registry_handle_global_remove(void *data, wl_registry *registry, uint32_t name) {}
186
187 const wl_registry_listener ShellWayland::registry_listener = {registry_handle_global, registry_handle_global_remove};
188
ShellWayland(Game & game)189 ShellWayland::ShellWayland(Game &game) : Shell(game) {
190 if (game.settings().validate) instance_layers_.push_back("VK_LAYER_LUNARG_standard_validation");
191 instance_extensions_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
192
193 init_connection();
194 init_vk();
195 }
196
~ShellWayland()197 ShellWayland::~ShellWayland() {
198 cleanup_vk();
199 dlclose(lib_handle_);
200
201 if (keyboard_) wl_keyboard_destroy(keyboard_);
202 if (pointer_) wl_pointer_destroy(pointer_);
203 if (seat_) wl_seat_destroy(seat_);
204 if (shell_surface_) wl_shell_surface_destroy(shell_surface_);
205 if (surface_) wl_surface_destroy(surface_);
206 if (shell_) wl_shell_destroy(shell_);
207 if (compositor_) wl_compositor_destroy(compositor_);
208 if (registry_) wl_registry_destroy(registry_);
209 if (display_) wl_display_disconnect(display_);
210 }
211
init_connection()212 void ShellWayland::init_connection() {
213 try {
214 display_ = wl_display_connect(NULL);
215 if (!display_) throw std::runtime_error("failed to connect to the display server");
216
217 registry_ = wl_display_get_registry(display_);
218 if (!registry_) throw std::runtime_error("failed to get registry");
219
220 wl_registry_add_listener(registry_, &ShellWayland::registry_listener, this);
221 wl_display_roundtrip(display_);
222
223 if (!compositor_) throw std::runtime_error("failed to bind compositor");
224
225 if (!shell_) throw std::runtime_error("failed to bind shell");
226 } catch (const std::exception &e) {
227 std::cerr << "Could not initialize Wayland: " << e.what() << std::endl;
228 exit(-1);
229 }
230 }
231
create_window()232 void ShellWayland::create_window() {
233 surface_ = wl_compositor_create_surface(compositor_);
234 if (!surface_) throw std::runtime_error("failed to create surface");
235
236 shell_surface_ = wl_shell_get_shell_surface(shell_, surface_);
237 if (!shell_surface_) throw std::runtime_error("failed to shell_surface");
238
239 wl_shell_surface_add_listener(shell_surface_, &ShellWayland::shell_surface_listener, this);
240 // set title
241 wl_shell_surface_set_title(shell_surface_, settings_.name.c_str());
242 wl_shell_surface_set_toplevel(shell_surface_);
243 }
244
load_vk()245 PFN_vkGetInstanceProcAddr ShellWayland::load_vk() {
246 const char filename[] = "libvulkan.so.1";
247 void *handle, *symbol;
248
249 #ifdef UNINSTALLED_LOADER
250 handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY);
251 if (!handle) handle = dlopen(filename, RTLD_LAZY);
252 #else
253 handle = dlopen(filename, RTLD_LAZY);
254 #endif
255
256 if (handle) symbol = dlsym(handle, "vkGetInstanceProcAddr");
257
258 if (!handle || !symbol) {
259 std::stringstream ss;
260 ss << "failed to load " << dlerror();
261
262 if (handle) dlclose(handle);
263
264 throw std::runtime_error(ss.str());
265 }
266
267 lib_handle_ = handle;
268
269 return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
270 }
271
can_present(VkPhysicalDevice phy,uint32_t queue_family)272 bool ShellWayland::can_present(VkPhysicalDevice phy, uint32_t queue_family) {
273 return vk::GetPhysicalDeviceWaylandPresentationSupportKHR(phy, queue_family, display_);
274 }
275
create_surface(VkInstance instance)276 VkSurfaceKHR ShellWayland::create_surface(VkInstance instance) {
277 VkWaylandSurfaceCreateInfoKHR surface_info = {};
278 surface_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
279 surface_info.display = display_;
280 surface_info.surface = surface_;
281
282 VkSurfaceKHR surface;
283 vk::assert_success(vk::CreateWaylandSurfaceKHR(instance, &surface_info, nullptr, &surface));
284
285 return surface;
286 }
287
loop_wait()288 void ShellWayland::loop_wait() {
289 while (true) {
290 if (quit_) break;
291
292 wl_display_dispatch_pending(display_);
293
294 acquire_back_buffer();
295 present_back_buffer();
296 }
297 }
298
loop_poll()299 void ShellWayland::loop_poll() {
300 PosixTimer timer;
301
302 double current_time = timer.get();
303 double profile_start_time = current_time;
304 int profile_present_count = 0;
305
306 while (true) {
307 if (quit_) break;
308
309 wl_display_dispatch_pending(display_);
310
311 acquire_back_buffer();
312
313 double t = timer.get();
314 add_game_time(static_cast<float>(t - current_time));
315
316 present_back_buffer();
317
318 current_time = t;
319
320 profile_present_count++;
321 if (current_time - profile_start_time >= 5.0) {
322 const double fps = profile_present_count / (current_time - profile_start_time);
323 std::stringstream ss;
324 ss << profile_present_count << " presents in " << current_time - profile_start_time << " seconds "
325 << "(FPS: " << fps << ")";
326 log(LOG_INFO, ss.str().c_str());
327
328 profile_start_time = current_time;
329 profile_present_count = 0;
330 }
331 }
332 }
333
run()334 void ShellWayland::run() {
335 create_window();
336 create_context();
337 resize_swapchain(settings_.initial_width, settings_.initial_height);
338
339 quit_ = false;
340 if (settings_.animate)
341 loop_poll();
342 else
343 loop_wait();
344
345 destroy_context();
346 }
347