• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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