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