blob: e83896744a2ba7d8f3def578a74045d29d8c32ba [file] [log] [blame]
Tony Barbour2f18b292016-02-25 15:44:10 -07001/*
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
32namespace {
33
34class PosixTimer {
35public:
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
67private:
68 struct timespec start_;
69};
70
71xcb_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
76xcb_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
90ShellXcb::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
98ShellXcb::~ShellXcb()
99{
100 cleanup_vk();
101 dlclose(lib_handle_);
102
103 xcb_disconnect(c_);
104}
105
106void 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
124void 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
160PFN_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
191bool ShellXcb::can_present(VkPhysicalDevice phy, uint32_t queue_family)
192{
193 return vk::GetPhysicalDeviceXcbPresentationSupportKHR(phy,
194 queue_family, c_, scr_->root_visual);
195}
196
197VkSurfaceKHR 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
210void 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
261void 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
279void 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
325void 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}