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 <sstream> 19 #include <dlfcn.h> 20 #include <time.h> 21 22 #include "Helpers.h" 23 #include "Game.h" 24 #include "ShellXcb.h" 25 26 namespace { 27 28 class PosixTimer { 29 public: 30 PosixTimer() 31 { 32 reset(); 33 } 34 35 void reset() 36 { 37 clock_gettime(CLOCK_MONOTONIC, &start_); 38 } 39 40 double get() const 41 { 42 struct timespec now; 43 clock_gettime(CLOCK_MONOTONIC, &now); 44 45 constexpr long one_s_in_ns = 1000 * 1000 * 1000; 46 constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns); 47 48 time_t s = now.tv_sec - start_.tv_sec; 49 long ns; 50 if (now.tv_nsec > start_.tv_nsec) { 51 ns = now.tv_nsec - start_.tv_nsec; 52 } else { 53 assert(s > 0); 54 s--; 55 ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec); 56 } 57 58 return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d; 59 } 60 61 private: 62 struct timespec start_; 63 }; 64 65 xcb_intern_atom_cookie_t intern_atom_cookie(xcb_connection_t *c, const std::string &s) 66 { 67 return xcb_intern_atom(c, false, s.size(), s.c_str()); 68 } 69 70 xcb_atom_t intern_atom(xcb_connection_t *c, xcb_intern_atom_cookie_t cookie) 71 { 72 xcb_atom_t atom = XCB_ATOM_NONE; 73 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie, nullptr); 74 if (reply) { 75 atom = reply->atom; 76 free(reply); 77 } 78 79 return atom; 80 } 81 82 } // namespace 83 84 ShellXcb::ShellXcb(Game &game) : Shell(game) 85 { 86 instance_extensions_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); 87 88 init_connection(); 89 init_vk(); 90 } 91 92 ShellXcb::~ShellXcb() 93 { 94 cleanup_vk(); 95 dlclose(lib_handle_); 96 97 xcb_disconnect(c_); 98 } 99 100 void ShellXcb::init_connection() 101 { 102 int scr; 103 104 c_ = xcb_connect(nullptr, &scr); 105 if (!c_ || xcb_connection_has_error(c_)) { 106 xcb_disconnect(c_); 107 throw std::runtime_error("failed to connect to the display server"); 108 } 109 110 const xcb_setup_t *setup = xcb_get_setup(c_); 111 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); 112 while (scr-- > 0) 113 xcb_screen_next(&iter); 114 115 scr_ = iter.data; 116 } 117 118 void ShellXcb::create_window() 119 { 120 win_ = xcb_generate_id(c_); 121 122 uint32_t value_mask, value_list[32]; 123 value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; 124 value_list[0] = scr_->black_pixel; 125 value_list[1] = XCB_EVENT_MASK_KEY_PRESS | 126 XCB_EVENT_MASK_STRUCTURE_NOTIFY; 127 128 xcb_create_window(c_, 129 XCB_COPY_FROM_PARENT, 130 win_, scr_->root, 0, 0, 131 settings_.initial_width, settings_.initial_height, 0, 132 XCB_WINDOW_CLASS_INPUT_OUTPUT, 133 scr_->root_visual, 134 value_mask, value_list); 135 136 xcb_intern_atom_cookie_t utf8_string_cookie = intern_atom_cookie(c_, "UTF8_STRING"); 137 xcb_intern_atom_cookie_t _net_wm_name_cookie = intern_atom_cookie(c_, "_NET_WM_NAME"); 138 xcb_intern_atom_cookie_t wm_protocols_cookie = intern_atom_cookie(c_, "WM_PROTOCOLS"); 139 xcb_intern_atom_cookie_t wm_delete_window_cookie = intern_atom_cookie(c_, "WM_DELETE_WINDOW"); 140 141 // set title 142 xcb_atom_t utf8_string = intern_atom(c_, utf8_string_cookie); 143 xcb_atom_t _net_wm_name = intern_atom(c_, _net_wm_name_cookie); 144 xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, _net_wm_name, 145 utf8_string, 8, settings_.name.size(), settings_.name.c_str()); 146 147 // advertise WM_DELETE_WINDOW 148 wm_protocols_ = intern_atom(c_, wm_protocols_cookie); 149 wm_delete_window_ = intern_atom(c_, wm_delete_window_cookie); 150 xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, wm_protocols_, 151 XCB_ATOM_ATOM, 32, 1, &wm_delete_window_); 152 } 153 154 PFN_vkGetInstanceProcAddr ShellXcb::load_vk() 155 { 156 const char filename[] = "libvulkan.so"; 157 void *handle, *symbol; 158 159 #ifdef UNINSTALLED_LOADER 160 handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY); 161 if (!handle) 162 handle = dlopen(filename, RTLD_LAZY); 163 #else 164 handle = dlopen(filename, RTLD_LAZY); 165 #endif 166 167 if (handle) 168 symbol = dlsym(handle, "vkGetInstanceProcAddr"); 169 170 if (!handle || !symbol) { 171 std::stringstream ss; 172 ss << "failed to load " << dlerror(); 173 174 if (handle) 175 dlclose(handle); 176 177 throw std::runtime_error(ss.str()); 178 } 179 180 lib_handle_ = handle; 181 182 return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol); 183 } 184 185 bool ShellXcb::can_present(VkPhysicalDevice phy, uint32_t queue_family) 186 { 187 return vk::GetPhysicalDeviceXcbPresentationSupportKHR(phy, 188 queue_family, c_, scr_->root_visual); 189 } 190 191 VkSurfaceKHR ShellXcb::create_surface(VkInstance instance) 192 { 193 VkXcbSurfaceCreateInfoKHR surface_info = {}; 194 surface_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; 195 surface_info.connection = c_; 196 surface_info.window = win_; 197 198 VkSurfaceKHR surface; 199 vk::assert_success(vk::CreateXcbSurfaceKHR(instance, &surface_info, nullptr, &surface)); 200 201 return surface; 202 } 203 204 void ShellXcb::handle_event(const xcb_generic_event_t *ev) 205 { 206 switch (ev->response_type & 0x7f) { 207 case XCB_CONFIGURE_NOTIFY: 208 { 209 const xcb_configure_notify_event_t *notify = 210 reinterpret_cast<const xcb_configure_notify_event_t *>(ev); 211 resize_swapchain(notify->width, notify->height); 212 } 213 break; 214 case XCB_KEY_PRESS: 215 { 216 const xcb_key_press_event_t *press = 217 reinterpret_cast<const xcb_key_press_event_t *>(ev); 218 Game::Key key; 219 220 // TODO translate xcb_keycode_t 221 switch (press->detail) { 222 case 9: 223 key = Game::KEY_ESC; 224 break; 225 case 111: 226 key = Game::KEY_UP; 227 break; 228 case 116: 229 key = Game::KEY_DOWN; 230 break; 231 case 65: 232 key = Game::KEY_SPACE; 233 break; 234 default: 235 key = Game::KEY_UNKNOWN; 236 break; 237 } 238 239 game_.on_key(key); 240 } 241 break; 242 case XCB_CLIENT_MESSAGE: 243 { 244 const xcb_client_message_event_t *msg = 245 reinterpret_cast<const xcb_client_message_event_t *>(ev); 246 if (msg->type == wm_protocols_ && msg->data.data32[0] == wm_delete_window_) 247 game_.on_key(Game::KEY_SHUTDOWN); 248 } 249 break; 250 default: 251 break; 252 } 253 } 254 255 void ShellXcb::loop_wait() 256 { 257 while (true) { 258 xcb_generic_event_t *ev = xcb_wait_for_event(c_); 259 if (!ev) 260 continue; 261 262 handle_event(ev); 263 free(ev); 264 265 if (quit_) 266 break; 267 268 acquire_back_buffer(); 269 present_back_buffer(); 270 } 271 } 272 273 void ShellXcb::loop_poll() 274 { 275 PosixTimer timer; 276 277 double current_time = timer.get(); 278 double profile_start_time = current_time; 279 int profile_present_count = 0; 280 281 while (true) { 282 // handle pending events 283 while (true) { 284 xcb_generic_event_t *ev = xcb_poll_for_event(c_); 285 if (!ev) 286 break; 287 288 handle_event(ev); 289 free(ev); 290 } 291 292 if (quit_) 293 break; 294 295 acquire_back_buffer(); 296 297 double t = timer.get(); 298 add_game_time(static_cast<float>(t - current_time)); 299 300 present_back_buffer(); 301 302 current_time = t; 303 304 profile_present_count++; 305 if (current_time - profile_start_time >= 5.0) { 306 const double fps = profile_present_count / (current_time - profile_start_time); 307 std::stringstream ss; 308 ss << profile_present_count << " presents in " << 309 current_time - profile_start_time << " seconds " << 310 "(FPS: " << fps << ")"; 311 log(LOG_INFO, ss.str().c_str()); 312 313 profile_start_time = current_time; 314 profile_present_count = 0; 315 } 316 } 317 } 318 319 void ShellXcb::run() 320 { 321 create_window(); 322 xcb_map_window(c_, win_); 323 xcb_flush(c_); 324 325 create_context(); 326 resize_swapchain(settings_.initial_width, settings_.initial_height); 327 328 quit_ = false; 329 if (settings_.animate) 330 loop_poll(); 331 else 332 loop_wait(); 333 334 destroy_context(); 335 336 xcb_destroy_window(c_, win_); 337 xcb_flush(c_); 338 } 339