Home | History | Annotate | Download | only in smoke
      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:
     30     PosixTimer()
     31     {
     32         reset();
     33     }
     34 
     35     void reset()
     36     {
     37         clock_gettime(CLOCK_MONOTONIC, &start_);
     38     }
     39 
     40     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 
     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 
     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 
     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 
     92 ShellXcb::~ShellXcb()
     93 {
     94     cleanup_vk();
     95     dlclose(lib_handle_);
     96 
     97     xcb_disconnect(c_);
     98 }
     99 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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