Home | History | Annotate | Download | only in smoke
      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:
     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 
     67 private:
     68     struct timespec start_;
     69 };
     70 
     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 
     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 
     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 
     98 ShellXcb::~ShellXcb()
     99 {
    100     cleanup_vk();
    101     dlclose(lib_handle_);
    102 
    103     xcb_disconnect(c_);
    104 }
    105 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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