1 /* 2 * Copyright (C) 2016 Google, Inc. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included 12 * in all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 * DEALINGS IN THE SOFTWARE. 21 */ 22 23 #include <cassert> 24 #include <sstream> 25 #include <dlfcn.h> 26 #include <time.h> 27 28 #include "Helpers.h" 29 #include "Game.h" 30 #include "ShellXcb.h" 31 32 namespace { 33 34 class PosixTimer { 35 public: 36 PosixTimer() 37 { 38 reset(); 39 } 40 41 void reset() 42 { 43 clock_gettime(CLOCK_MONOTONIC, &start_); 44 } 45 46 double get() const 47 { 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 xcb_intern_atom_cookie_t intern_atom_cookie(xcb_connection_t *c, const std::string &s) 72 { 73 return xcb_intern_atom(c, false, s.size(), s.c_str()); 74 } 75 76 xcb_atom_t intern_atom(xcb_connection_t *c, xcb_intern_atom_cookie_t cookie) 77 { 78 xcb_atom_t atom = XCB_ATOM_NONE; 79 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie, nullptr); 80 if (reply) { 81 atom = reply->atom; 82 free(reply); 83 } 84 85 return atom; 86 } 87 88 } // namespace 89 90 ShellXcb::ShellXcb(Game &game) : Shell(game) 91 { 92 instance_extensions_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); 93 94 init_connection(); 95 init_vk(); 96 } 97 98 ShellXcb::~ShellXcb() 99 { 100 cleanup_vk(); 101 dlclose(lib_handle_); 102 103 xcb_disconnect(c_); 104 } 105 106 void ShellXcb::init_connection() 107 { 108 int scr; 109 110 c_ = xcb_connect(nullptr, &scr); 111 if (!c_ || xcb_connection_has_error(c_)) { 112 xcb_disconnect(c_); 113 throw std::runtime_error("failed to connect to the display server"); 114 } 115 116 const xcb_setup_t *setup = xcb_get_setup(c_); 117 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); 118 while (scr-- > 0) 119 xcb_screen_next(&iter); 120 121 scr_ = iter.data; 122 } 123 124 void ShellXcb::create_window() 125 { 126 win_ = xcb_generate_id(c_); 127 128 uint32_t value_mask, value_list[32]; 129 value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; 130 value_list[0] = scr_->black_pixel; 131 value_list[1] = XCB_EVENT_MASK_KEY_PRESS | 132 XCB_EVENT_MASK_STRUCTURE_NOTIFY; 133 134 xcb_create_window(c_, 135 XCB_COPY_FROM_PARENT, 136 win_, scr_->root, 0, 0, 137 settings_.initial_width, settings_.initial_height, 0, 138 XCB_WINDOW_CLASS_INPUT_OUTPUT, 139 scr_->root_visual, 140 value_mask, value_list); 141 142 xcb_intern_atom_cookie_t utf8_string_cookie = intern_atom_cookie(c_, "UTF8_STRING"); 143 xcb_intern_atom_cookie_t _net_wm_name_cookie = intern_atom_cookie(c_, "_NET_WM_NAME"); 144 xcb_intern_atom_cookie_t wm_protocols_cookie = intern_atom_cookie(c_, "WM_PROTOCOLS"); 145 xcb_intern_atom_cookie_t wm_delete_window_cookie = intern_atom_cookie(c_, "WM_DELETE_WINDOW"); 146 147 // set title 148 xcb_atom_t utf8_string = intern_atom(c_, utf8_string_cookie); 149 xcb_atom_t _net_wm_name = intern_atom(c_, _net_wm_name_cookie); 150 xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, _net_wm_name, 151 utf8_string, 8, settings_.name.size(), settings_.name.c_str()); 152 153 // advertise WM_DELETE_WINDOW 154 wm_protocols_ = intern_atom(c_, wm_protocols_cookie); 155 wm_delete_window_ = intern_atom(c_, wm_delete_window_cookie); 156 xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, wm_protocols_, 157 XCB_ATOM_ATOM, 32, 1, &wm_delete_window_); 158 } 159 160 PFN_vkGetInstanceProcAddr ShellXcb::load_vk() 161 { 162 const char filename[] = "libvulkan.so"; 163 void *handle, *symbol; 164 165 #ifdef UNINSTALLED_LOADER 166 handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY); 167 if (!handle) 168 handle = dlopen(filename, RTLD_LAZY); 169 #else 170 handle = dlopen(filename, RTLD_LAZY); 171 #endif 172 173 if (handle) 174 symbol = dlsym(handle, "vkGetInstanceProcAddr"); 175 176 if (!handle || !symbol) { 177 std::stringstream ss; 178 ss << "failed to load " << dlerror(); 179 180 if (handle) 181 dlclose(handle); 182 183 throw std::runtime_error(ss.str()); 184 } 185 186 lib_handle_ = handle; 187 188 return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol); 189 } 190 191 bool ShellXcb::can_present(VkPhysicalDevice phy, uint32_t queue_family) 192 { 193 return vk::GetPhysicalDeviceXcbPresentationSupportKHR(phy, 194 queue_family, c_, scr_->root_visual); 195 } 196 197 VkSurfaceKHR ShellXcb::create_surface(VkInstance instance) 198 { 199 VkXcbSurfaceCreateInfoKHR surface_info = {}; 200 surface_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; 201 surface_info.connection = c_; 202 surface_info.window = win_; 203 204 VkSurfaceKHR surface; 205 vk::assert_success(vk::CreateXcbSurfaceKHR(instance, &surface_info, nullptr, &surface)); 206 207 return surface; 208 } 209 210 void ShellXcb::handle_event(const xcb_generic_event_t *ev) 211 { 212 switch (ev->response_type & 0x7f) { 213 case XCB_CONFIGURE_NOTIFY: 214 { 215 const xcb_configure_notify_event_t *notify = 216 reinterpret_cast<const xcb_configure_notify_event_t *>(ev); 217 resize_swapchain(notify->width, notify->height); 218 } 219 break; 220 case XCB_KEY_PRESS: 221 { 222 const xcb_key_press_event_t *press = 223 reinterpret_cast<const xcb_key_press_event_t *>(ev); 224 Game::Key key; 225 226 // TODO translate xcb_keycode_t 227 switch (press->detail) { 228 case 9: 229 key = Game::KEY_ESC; 230 break; 231 case 111: 232 key = Game::KEY_UP; 233 break; 234 case 116: 235 key = Game::KEY_DOWN; 236 break; 237 case 65: 238 key = Game::KEY_SPACE; 239 break; 240 default: 241 key = Game::KEY_UNKNOWN; 242 break; 243 } 244 245 game_.on_key(key); 246 } 247 break; 248 case XCB_CLIENT_MESSAGE: 249 { 250 const xcb_client_message_event_t *msg = 251 reinterpret_cast<const xcb_client_message_event_t *>(ev); 252 if (msg->type == wm_protocols_ && msg->data.data32[0] == wm_delete_window_) 253 game_.on_key(Game::KEY_SHUTDOWN); 254 } 255 break; 256 default: 257 break; 258 } 259 } 260 261 void ShellXcb::loop_wait() 262 { 263 while (true) { 264 xcb_generic_event_t *ev = xcb_wait_for_event(c_); 265 if (!ev) 266 continue; 267 268 handle_event(ev); 269 free(ev); 270 271 if (quit_) 272 break; 273 274 acquire_back_buffer(); 275 present_back_buffer(); 276 } 277 } 278 279 void ShellXcb::loop_poll() 280 { 281 PosixTimer timer; 282 283 double current_time = timer.get(); 284 double profile_start_time = current_time; 285 int profile_present_count = 0; 286 287 while (true) { 288 // handle pending events 289 while (true) { 290 xcb_generic_event_t *ev = xcb_poll_for_event(c_); 291 if (!ev) 292 break; 293 294 handle_event(ev); 295 free(ev); 296 } 297 298 if (quit_) 299 break; 300 301 acquire_back_buffer(); 302 303 double t = timer.get(); 304 add_game_time(static_cast<float>(t - current_time)); 305 306 present_back_buffer(); 307 308 current_time = t; 309 310 profile_present_count++; 311 if (current_time - profile_start_time >= 5.0) { 312 const double fps = profile_present_count / (current_time - profile_start_time); 313 std::stringstream ss; 314 ss << profile_present_count << " presents in " << 315 current_time - profile_start_time << " seconds " << 316 "(FPS: " << fps << ")"; 317 log(LOG_INFO, ss.str().c_str()); 318 319 profile_start_time = current_time; 320 profile_present_count = 0; 321 } 322 } 323 } 324 325 void ShellXcb::run() 326 { 327 create_window(); 328 xcb_map_window(c_, win_); 329 xcb_flush(c_); 330 331 create_context(); 332 resize_swapchain(settings_.initial_width, settings_.initial_height); 333 334 quit_ = false; 335 if (settings_.animate) 336 loop_poll(); 337 else 338 loop_wait(); 339 340 destroy_context(); 341 342 xcb_destroy_window(c_, win_); 343 xcb_flush(c_); 344 } 345