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 <dlfcn.h>
     19 #include <sstream>
     20 #include <time.h>
     21 
     22 #include "Game.h"
     23 #include "Helpers.h"
     24 #include "ShellWayland.h"
     25 #include <stdio.h>
     26 #include <string.h>
     27 #include <linux/input.h>
     28 
     29 /* Unused attribute / variable MACRO.
     30    Some methods of classes' heirs do not need all fuction parameters.
     31    This triggers warning on GCC platfoms. This macro will silence them.
     32 */
     33 #if defined(__GNUC__)
     34 #define UNUSED __attribute__((unused))
     35 #else
     36 #define UNUSED
     37 #endif
     38 
     39 namespace {
     40 
     41 class PosixTimer {
     42    public:
     43     PosixTimer() { reset(); }
     44 
     45     void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); }
     46 
     47     double get() const {
     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 }  // namespace
     72 
     73 void ShellWayland::handle_ping(void *data, wl_shell_surface *shell_surface, uint32_t serial) {
     74     wl_shell_surface_pong(shell_surface, serial);
     75 }
     76 
     77 void ShellWayland::handle_configure(void *data, wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) {}
     78 
     79 void ShellWayland::handle_popup_done(void *data, wl_shell_surface *shell_surface) {}
     80 
     81 const wl_shell_surface_listener ShellWayland::shell_surface_listener = {handle_ping, handle_configure, handle_popup_done};
     82 
     83 void ShellWayland::pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface,
     84                                         wl_fixed_t sx, wl_fixed_t sy) {}
     85 
     86 void ShellWayland::pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {}
     87 
     88 void ShellWayland::pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {}
     89 
     90 void ShellWayland::pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button,
     91                                          uint32_t state) {
     92     if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) {
     93         ShellWayland *shell = (ShellWayland *)data;
     94         wl_shell_surface_move(shell->shell_surface_, shell->seat_, serial);
     95     }
     96 }
     97 
     98 void ShellWayland::pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {}
     99 
    100 const wl_pointer_listener ShellWayland::pointer_listener = {
    101     pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis,
    102 };
    103 
    104 void ShellWayland::keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {}
    105 
    106 void ShellWayland::keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface,
    107                                          struct wl_array *keys) {}
    108 
    109 void ShellWayland::keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {}
    110 
    111 void ShellWayland::keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key,
    112                                        uint32_t state) {
    113     if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return;
    114     ShellWayland *shell = (ShellWayland *)data;
    115     Game::Key game_key;
    116     switch (key) {
    117         case KEY_ESC:  // Escape
    118 #undef KEY_ESC
    119             game_key = Game::KEY_ESC;
    120             break;
    121         case KEY_UP:  // up arrow key
    122 #undef KEY_UP
    123             game_key = Game::KEY_UP;
    124             break;
    125         case KEY_DOWN:  // right arrow key
    126 #undef KEY_DOWN
    127             game_key = Game::KEY_DOWN;
    128             break;
    129         case KEY_SPACE:  // space bar
    130 #undef KEY_SPACE
    131             game_key = Game::KEY_SPACE;
    132             break;
    133         default:
    134 #undef KEY_UNKNOWN
    135             game_key = Game::KEY_UNKNOWN;
    136             break;
    137     }
    138     shell->game_.on_key(game_key);
    139 }
    140 
    141 void ShellWayland::keyboard_handle_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed,
    142                                              uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {}
    143 
    144 const wl_keyboard_listener ShellWayland::keyboard_listener = {
    145     keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers,
    146 };
    147 
    148 void ShellWayland::seat_handle_capabilities(void *data, wl_seat *seat, uint32_t caps) {
    149     // Subscribe to pointer events
    150     ShellWayland *shell = (ShellWayland *)data;
    151     if ((caps & WL_SEAT_CAPABILITY_POINTER) && !shell->pointer_) {
    152         shell->pointer_ = wl_seat_get_pointer(seat);
    153         wl_pointer_add_listener(shell->pointer_, &pointer_listener, shell);
    154     } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && shell->pointer_) {
    155         wl_pointer_destroy(shell->pointer_);
    156         shell->pointer_ = NULL;
    157     }
    158     // Subscribe to keyboard events
    159     if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
    160         shell->keyboard_ = wl_seat_get_keyboard(seat);
    161         wl_keyboard_add_listener(shell->keyboard_, &keyboard_listener, shell);
    162     } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
    163         wl_keyboard_destroy(shell->keyboard_);
    164         shell->keyboard_ = NULL;
    165     }
    166 }
    167 
    168 const wl_seat_listener ShellWayland::seat_listener = {
    169     seat_handle_capabilities,
    170 };
    171 
    172 void ShellWayland::registry_handle_global(void *data, wl_registry *registry, uint32_t id, const char *interface, uint32_t version) {
    173     // pickup wayland objects when they appear
    174     ShellWayland *shell = (ShellWayland *)data;
    175     if (strcmp(interface, "wl_compositor") == 0) {
    176         shell->compositor_ = (wl_compositor *)wl_registry_bind(registry, id, &wl_compositor_interface, 1);
    177     } else if (strcmp(interface, "wl_shell") == 0) {
    178         shell->shell_ = (wl_shell *)wl_registry_bind(registry, id, &wl_shell_interface, 1);
    179     } else if (strcmp(interface, "wl_seat") == 0) {
    180         shell->seat_ = (wl_seat *)wl_registry_bind(registry, id, &wl_seat_interface, 1);
    181         wl_seat_add_listener(shell->seat_, &seat_listener, shell);
    182     }
    183 }
    184 
    185 void ShellWayland::registry_handle_global_remove(void *data, wl_registry *registry, uint32_t name) {}
    186 
    187 const wl_registry_listener ShellWayland::registry_listener = {registry_handle_global, registry_handle_global_remove};
    188 
    189 ShellWayland::ShellWayland(Game &game) : Shell(game) {
    190     if (game.settings().validate) instance_layers_.push_back("VK_LAYER_LUNARG_standard_validation");
    191     instance_extensions_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
    192 
    193     init_connection();
    194     init_vk();
    195 }
    196 
    197 ShellWayland::~ShellWayland() {
    198     cleanup_vk();
    199     dlclose(lib_handle_);
    200 
    201     if (keyboard_) wl_keyboard_destroy(keyboard_);
    202     if (pointer_) wl_pointer_destroy(pointer_);
    203     if (seat_) wl_seat_destroy(seat_);
    204     if (shell_surface_) wl_shell_surface_destroy(shell_surface_);
    205     if (surface_) wl_surface_destroy(surface_);
    206     if (shell_) wl_shell_destroy(shell_);
    207     if (compositor_) wl_compositor_destroy(compositor_);
    208     if (registry_) wl_registry_destroy(registry_);
    209     if (display_) wl_display_disconnect(display_);
    210 }
    211 
    212 void ShellWayland::init_connection() {
    213     try {
    214         display_ = wl_display_connect(NULL);
    215         if (!display_) throw std::runtime_error("failed to connect to the display server");
    216 
    217         registry_ = wl_display_get_registry(display_);
    218         if (!registry_) throw std::runtime_error("failed to get registry");
    219 
    220         wl_registry_add_listener(registry_, &ShellWayland::registry_listener, this);
    221         wl_display_roundtrip(display_);
    222 
    223         if (!compositor_) throw std::runtime_error("failed to bind compositor");
    224 
    225         if (!shell_) throw std::runtime_error("failed to bind shell");
    226     } catch (const std::exception &e) {
    227         std::cerr << "Could not initialize Wayland: " << e.what() << std::endl;
    228         exit(-1);
    229     }
    230 }
    231 
    232 void ShellWayland::create_window() {
    233     surface_ = wl_compositor_create_surface(compositor_);
    234     if (!surface_) throw std::runtime_error("failed to create surface");
    235 
    236     shell_surface_ = wl_shell_get_shell_surface(shell_, surface_);
    237     if (!shell_surface_) throw std::runtime_error("failed to shell_surface");
    238 
    239     wl_shell_surface_add_listener(shell_surface_, &ShellWayland::shell_surface_listener, this);
    240     // set title
    241     wl_shell_surface_set_title(shell_surface_, settings_.name.c_str());
    242     wl_shell_surface_set_toplevel(shell_surface_);
    243 }
    244 
    245 PFN_vkGetInstanceProcAddr ShellWayland::load_vk() {
    246     const char filename[] = "libvulkan.so.1";
    247     void *handle, *symbol;
    248 
    249 #ifdef UNINSTALLED_LOADER
    250     handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY);
    251     if (!handle) handle = dlopen(filename, RTLD_LAZY);
    252 #else
    253     handle = dlopen(filename, RTLD_LAZY);
    254 #endif
    255 
    256     if (handle) symbol = dlsym(handle, "vkGetInstanceProcAddr");
    257 
    258     if (!handle || !symbol) {
    259         std::stringstream ss;
    260         ss << "failed to load " << dlerror();
    261 
    262         if (handle) dlclose(handle);
    263 
    264         throw std::runtime_error(ss.str());
    265     }
    266 
    267     lib_handle_ = handle;
    268 
    269     return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
    270 }
    271 
    272 bool ShellWayland::can_present(VkPhysicalDevice phy, uint32_t queue_family) {
    273     return vk::GetPhysicalDeviceWaylandPresentationSupportKHR(phy, queue_family, display_);
    274 }
    275 
    276 VkSurfaceKHR ShellWayland::create_surface(VkInstance instance) {
    277     VkWaylandSurfaceCreateInfoKHR surface_info = {};
    278     surface_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
    279     surface_info.display = display_;
    280     surface_info.surface = surface_;
    281 
    282     VkSurfaceKHR surface;
    283     vk::assert_success(vk::CreateWaylandSurfaceKHR(instance, &surface_info, nullptr, &surface));
    284 
    285     return surface;
    286 }
    287 
    288 void ShellWayland::loop_wait() {
    289     while (true) {
    290         if (quit_) break;
    291 
    292         wl_display_dispatch_pending(display_);
    293 
    294         acquire_back_buffer();
    295         present_back_buffer();
    296     }
    297 }
    298 
    299 void ShellWayland::loop_poll() {
    300     PosixTimer timer;
    301 
    302     double current_time = timer.get();
    303     double profile_start_time = current_time;
    304     int profile_present_count = 0;
    305 
    306     while (true) {
    307         if (quit_) break;
    308 
    309         wl_display_dispatch_pending(display_);
    310 
    311         acquire_back_buffer();
    312 
    313         double t = timer.get();
    314         add_game_time(static_cast<float>(t - current_time));
    315 
    316         present_back_buffer();
    317 
    318         current_time = t;
    319 
    320         profile_present_count++;
    321         if (current_time - profile_start_time >= 5.0) {
    322             const double fps = profile_present_count / (current_time - profile_start_time);
    323             std::stringstream ss;
    324             ss << profile_present_count << " presents in " << current_time - profile_start_time << " seconds "
    325                << "(FPS: " << fps << ")";
    326             log(LOG_INFO, ss.str().c_str());
    327 
    328             profile_start_time = current_time;
    329             profile_present_count = 0;
    330         }
    331     }
    332 }
    333 
    334 void ShellWayland::run() {
    335     create_window();
    336     create_context();
    337     resize_swapchain(settings_.initial_width, settings_.initial_height);
    338 
    339     quit_ = false;
    340     if (settings_.animate)
    341         loop_poll();
    342     else
    343         loop_wait();
    344 
    345     destroy_context();
    346 }
    347