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 28 /* Unused attribute / variable MACRO. 29 Some methods of classes' heirs do not need all fuction parameters. 30 This triggers warning on GCC platfoms. This macro will silence them. 31 */ 32 #if defined(__GNUC__) 33 #define UNUSED __attribute__((unused)) 34 #else 35 #define UNUSED 36 #endif 37 38 namespace { 39 40 class PosixTimer { 41 public: 42 PosixTimer() { reset(); } 43 44 void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); } 45 46 double get() const { 47 struct timespec now; 48 clock_gettime(CLOCK_MONOTONIC, &now); 49 50 constexpr long one_s_in_ns = 1000 * 1000 * 1000; 51 constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns); 52 53 time_t s = now.tv_sec - start_.tv_sec; 54 long ns; 55 if (now.tv_nsec > start_.tv_nsec) { 56 ns = now.tv_nsec - start_.tv_nsec; 57 } else { 58 assert(s > 0); 59 s--; 60 ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec); 61 } 62 63 return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d; 64 } 65 66 private: 67 struct timespec start_; 68 }; 69 70 } // namespace 71 72 const struct wl_registry_listener ShellWayland::registry_listener_ = { 73 ShellWayland::handle_global, ShellWayland::handle_global_remove}; 74 75 const struct wl_shell_surface_listener ShellWayland::shell_surface_listener_ = { 76 ShellWayland::handle_ping, ShellWayland::handle_configure, 77 ShellWayland::handle_popup_done}; 78 79 void ShellWayland::handle_global(void *data, struct wl_registry *registry, 80 uint32_t id, const char *interface, 81 uint32_t version UNUSED) { 82 ShellWayland *_this = static_cast<ShellWayland *>(data); 83 84 if (!strcmp(interface, "wl_compositor")) 85 _this->compositor_ = static_cast<struct wl_compositor *>( 86 wl_registry_bind(registry, id, &wl_compositor_interface, 3)); 87 /* Todo: When xdg_shell protocol has stablized, we should move wl_shell tp 88 * xdg_shell */ 89 else if (!strcmp(interface, "wl_shell")) 90 _this->shell_ = static_cast<struct wl_shell *>( 91 wl_registry_bind(registry, id, &wl_shell_interface, 1)); 92 } 93 94 void ShellWayland::handle_global_remove(void *data UNUSED, 95 struct wl_registry *registry UNUSED, 96 uint32_t name UNUSED) {} 97 98 void ShellWayland::handle_ping(void *data UNUSED, 99 struct wl_shell_surface *shell_surface, 100 uint32_t serial) { 101 wl_shell_surface_pong(shell_surface, serial); 102 } 103 104 void ShellWayland::handle_configure( 105 void *data UNUSED, struct wl_shell_surface *shell_surface UNUSED, 106 uint32_t edges UNUSED, int32_t width UNUSED, int32_t height UNUSED) {} 107 108 void ShellWayland::handle_popup_done( 109 void *data UNUSED, struct wl_shell_surface *shell_surface UNUSED) {} 110 111 ShellWayland::ShellWayland(Game &game) : Shell(game) { 112 instance_extensions_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); 113 114 init_connection(); 115 init_vk(); 116 } 117 118 ShellWayland::~ShellWayland() { 119 cleanup_vk(); 120 dlclose(lib_handle_); 121 122 if (shell_surface_) 123 wl_shell_surface_destroy(shell_surface_); 124 if (surface_) 125 wl_surface_destroy(surface_); 126 if (shell_) 127 wl_shell_destroy(shell_); 128 if (compositor_) 129 wl_compositor_destroy(compositor_); 130 if (registry_) 131 wl_registry_destroy(registry_); 132 if (display_) 133 wl_display_disconnect(display_); 134 } 135 136 void ShellWayland::init_connection() { 137 try { 138 display_ = wl_display_connect(NULL); 139 if (!display_) 140 throw std::runtime_error("failed to connect to the display server"); 141 142 registry_ = wl_display_get_registry(display_); 143 if (!registry_) 144 throw std::runtime_error("failed to get registry"); 145 146 wl_registry_add_listener(registry_, ®istry_listener_, this); 147 wl_display_roundtrip(display_); 148 149 if (!compositor_) 150 throw std::runtime_error("failed to bind compositor"); 151 152 if (!shell_) 153 throw std::runtime_error("failed to bind shell"); 154 } catch (...) { 155 if (shell_) 156 wl_shell_destroy(shell_); 157 if (compositor_) 158 wl_compositor_destroy(compositor_); 159 if (registry_) 160 wl_registry_destroy(registry_); 161 if (display_) 162 wl_display_disconnect(display_); 163 164 throw; 165 } 166 } 167 168 void ShellWayland::create_window() { 169 surface_ = wl_compositor_create_surface(compositor_); 170 if (!surface_) 171 throw std::runtime_error("failed to create surface"); 172 173 shell_surface_ = wl_shell_get_shell_surface(shell_, surface_); 174 if (!shell_surface_) 175 throw std::runtime_error("failed to shell_surface"); 176 177 wl_shell_surface_add_listener(shell_surface_, &shell_surface_listener_, 178 this); 179 // set title 180 wl_shell_surface_set_title(shell_surface_, settings_.name.c_str()); 181 wl_shell_surface_set_toplevel(shell_surface_); 182 } 183 184 PFN_vkGetInstanceProcAddr ShellWayland::load_vk() { 185 const char filename[] = "libvulkan.so"; 186 void *handle, *symbol; 187 188 #ifdef UNINSTALLED_LOADER 189 handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY); 190 if (!handle) 191 handle = dlopen(filename, RTLD_LAZY); 192 #else 193 handle = dlopen(filename, RTLD_LAZY); 194 #endif 195 196 if (handle) 197 symbol = dlsym(handle, "vkGetInstanceProcAddr"); 198 199 if (!handle || !symbol) { 200 std::stringstream ss; 201 ss << "failed to load " << dlerror(); 202 203 if (handle) 204 dlclose(handle); 205 206 throw std::runtime_error(ss.str()); 207 } 208 209 lib_handle_ = handle; 210 211 return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol); 212 } 213 214 bool ShellWayland::can_present(VkPhysicalDevice phy, uint32_t queue_family) { 215 return vk::GetPhysicalDeviceWaylandPresentationSupportKHR(phy, queue_family, 216 display_); 217 } 218 219 VkSurfaceKHR ShellWayland::create_surface(VkInstance instance) { 220 VkWaylandSurfaceCreateInfoKHR surface_info = {}; 221 surface_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; 222 surface_info.display = display_; 223 surface_info.surface = surface_; 224 225 VkSurfaceKHR surface; 226 vk::assert_success(vk::CreateWaylandSurfaceKHR(instance, &surface_info, 227 nullptr, &surface)); 228 229 return surface; 230 } 231 232 void ShellWayland::loop_wait() { 233 while (true) { 234 if (quit_) 235 break; 236 237 acquire_back_buffer(); 238 present_back_buffer(); 239 } 240 } 241 242 void ShellWayland::loop_poll() { 243 PosixTimer timer; 244 245 double current_time = timer.get(); 246 double profile_start_time = current_time; 247 int profile_present_count = 0; 248 249 while (true) { 250 if (quit_) 251 break; 252 253 acquire_back_buffer(); 254 255 double t = timer.get(); 256 add_game_time(static_cast<float>(t - current_time)); 257 258 present_back_buffer(); 259 260 current_time = t; 261 262 profile_present_count++; 263 if (current_time - profile_start_time >= 5.0) { 264 const double fps = 265 profile_present_count / (current_time - profile_start_time); 266 std::stringstream ss; 267 ss << profile_present_count << " presents in " 268 << current_time - profile_start_time << " seconds " 269 << "(FPS: " << fps << ")"; 270 log(LOG_INFO, ss.str().c_str()); 271 272 profile_start_time = current_time; 273 profile_present_count = 0; 274 } 275 } 276 } 277 278 void ShellWayland::run() { 279 create_window(); 280 create_context(); 281 resize_swapchain(settings_.initial_width, settings_.initial_height); 282 283 quit_ = false; 284 if (settings_.animate) 285 loop_poll(); 286 else 287 loop_wait(); 288 289 destroy_context(); 290 } 291