• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
PosixTimer()36     PosixTimer()
37     {
38         reset();
39     }
40 
reset()41     void reset()
42     {
43         clock_gettime(CLOCK_MONOTONIC, &start_);
44     }
45 
get() const46     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 
intern_atom_cookie(xcb_connection_t * c,const std::string & s)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 
intern_atom(xcb_connection_t * c,xcb_intern_atom_cookie_t cookie)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 
ShellXcb(Game & game)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 
~ShellXcb()98 ShellXcb::~ShellXcb()
99 {
100     cleanup_vk();
101     dlclose(lib_handle_);
102 
103     xcb_disconnect(c_);
104 }
105 
init_connection()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 
create_window()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 
load_vk()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 
can_present(VkPhysicalDevice phy,uint32_t queue_family)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 
create_surface(VkInstance instance)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 
handle_event(const xcb_generic_event_t * ev)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 
loop_wait()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 
loop_poll()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 
run()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