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: 43 PosixTimer() { reset(); } 44 45 void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); } 46 47 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 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 77 void ShellWayland::handle_configure(void *data, wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) {} 78 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 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 86 void ShellWayland::pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {} 87 88 void ShellWayland::pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {} 89 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 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 104 void ShellWayland::keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {} 105 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 109 void ShellWayland::keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {} 110 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 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 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 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 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 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 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 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 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 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 272 bool ShellWayland::can_present(VkPhysicalDevice phy, uint32_t queue_family) { 273 return vk::GetPhysicalDeviceWaylandPresentationSupportKHR(phy, queue_family, display_); 274 } 275 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 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 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 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