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:
PosixTimer()30 PosixTimer()
31 {
32 reset();
33 }
34
reset()35 void reset()
36 {
37 clock_gettime(CLOCK_MONOTONIC, &start_);
38 }
39
get() const40 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
intern_atom_cookie(xcb_connection_t * c,const std::string & s)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
intern_atom(xcb_connection_t * c,xcb_intern_atom_cookie_t cookie)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
ShellXcb(Game & game)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
~ShellXcb()92 ShellXcb::~ShellXcb()
93 {
94 cleanup_vk();
95 dlclose(lib_handle_);
96
97 xcb_disconnect(c_);
98 }
99
init_connection()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
create_window()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
load_vk()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
can_present(VkPhysicalDevice phy,uint32_t queue_family)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
create_surface(VkInstance instance)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
handle_event(const xcb_generic_event_t * ev)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
loop_wait()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
loop_poll()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
run()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