• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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