diff options
Diffstat (limited to 'demos/smoke/ShellXcb.cpp')
| -rw-r--r-- | demos/smoke/ShellXcb.cpp | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/demos/smoke/ShellXcb.cpp b/demos/smoke/ShellXcb.cpp new file mode 100644 index 00000000..e8389674 --- /dev/null +++ b/demos/smoke/ShellXcb.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2016 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <cassert> +#include <sstream> +#include <dlfcn.h> +#include <time.h> + +#include "Helpers.h" +#include "Game.h" +#include "ShellXcb.h" + +namespace { + +class PosixTimer { +public: + PosixTimer() + { + reset(); + } + + void reset() + { + clock_gettime(CLOCK_MONOTONIC, &start_); + } + + double get() const + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + constexpr long one_s_in_ns = 1000 * 1000 * 1000; + constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns); + + time_t s = now.tv_sec - start_.tv_sec; + long ns; + if (now.tv_nsec > start_.tv_nsec) { + ns = now.tv_nsec - start_.tv_nsec; + } else { + assert(s > 0); + s--; + ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec); + } + + return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d; + } + +private: + struct timespec start_; +}; + +xcb_intern_atom_cookie_t intern_atom_cookie(xcb_connection_t *c, const std::string &s) +{ + return xcb_intern_atom(c, false, s.size(), s.c_str()); +} + +xcb_atom_t intern_atom(xcb_connection_t *c, xcb_intern_atom_cookie_t cookie) +{ + xcb_atom_t atom = XCB_ATOM_NONE; + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie, nullptr); + if (reply) { + atom = reply->atom; + free(reply); + } + + return atom; +} + +} // namespace + +ShellXcb::ShellXcb(Game &game) : Shell(game) +{ + instance_extensions_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); + + init_connection(); + init_vk(); +} + +ShellXcb::~ShellXcb() +{ + cleanup_vk(); + dlclose(lib_handle_); + + xcb_disconnect(c_); +} + +void ShellXcb::init_connection() +{ + int scr; + + c_ = xcb_connect(nullptr, &scr); + if (!c_ || xcb_connection_has_error(c_)) { + xcb_disconnect(c_); + throw std::runtime_error("failed to connect to the display server"); + } + + const xcb_setup_t *setup = xcb_get_setup(c_); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + while (scr-- > 0) + xcb_screen_next(&iter); + + scr_ = iter.data; +} + +void ShellXcb::create_window() +{ + win_ = xcb_generate_id(c_); + + uint32_t value_mask, value_list[32]; + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = scr_->black_pixel; + value_list[1] = XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(c_, + XCB_COPY_FROM_PARENT, + win_, scr_->root, 0, 0, + settings_.initial_width, settings_.initial_height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + scr_->root_visual, + value_mask, value_list); + + xcb_intern_atom_cookie_t utf8_string_cookie = intern_atom_cookie(c_, "UTF8_STRING"); + xcb_intern_atom_cookie_t _net_wm_name_cookie = intern_atom_cookie(c_, "_NET_WM_NAME"); + xcb_intern_atom_cookie_t wm_protocols_cookie = intern_atom_cookie(c_, "WM_PROTOCOLS"); + xcb_intern_atom_cookie_t wm_delete_window_cookie = intern_atom_cookie(c_, "WM_DELETE_WINDOW"); + + // set title + xcb_atom_t utf8_string = intern_atom(c_, utf8_string_cookie); + xcb_atom_t _net_wm_name = intern_atom(c_, _net_wm_name_cookie); + xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, _net_wm_name, + utf8_string, 8, settings_.name.size(), settings_.name.c_str()); + + // advertise WM_DELETE_WINDOW + wm_protocols_ = intern_atom(c_, wm_protocols_cookie); + wm_delete_window_ = intern_atom(c_, wm_delete_window_cookie); + xcb_change_property(c_, XCB_PROP_MODE_REPLACE, win_, wm_protocols_, + XCB_ATOM_ATOM, 32, 1, &wm_delete_window_); +} + +PFN_vkGetInstanceProcAddr ShellXcb::load_vk() +{ + const char filename[] = "libvulkan.so"; + void *handle, *symbol; + +#ifdef UNINSTALLED_LOADER + handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY); + if (!handle) + handle = dlopen(filename, RTLD_LAZY); +#else + handle = dlopen(filename, RTLD_LAZY); +#endif + + if (handle) + symbol = dlsym(handle, "vkGetInstanceProcAddr"); + + if (!handle || !symbol) { + std::stringstream ss; + ss << "failed to load " << dlerror(); + + if (handle) + dlclose(handle); + + throw std::runtime_error(ss.str()); + } + + lib_handle_ = handle; + + return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol); +} + +bool ShellXcb::can_present(VkPhysicalDevice phy, uint32_t queue_family) +{ + return vk::GetPhysicalDeviceXcbPresentationSupportKHR(phy, + queue_family, c_, scr_->root_visual); +} + +VkSurfaceKHR ShellXcb::create_surface(VkInstance instance) +{ + VkXcbSurfaceCreateInfoKHR surface_info = {}; + surface_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + surface_info.connection = c_; + surface_info.window = win_; + + VkSurfaceKHR surface; + vk::assert_success(vk::CreateXcbSurfaceKHR(instance, &surface_info, nullptr, &surface)); + + return surface; +} + +void ShellXcb::handle_event(const xcb_generic_event_t *ev) +{ + switch (ev->response_type & 0x7f) { + case XCB_CONFIGURE_NOTIFY: + { + const xcb_configure_notify_event_t *notify = + reinterpret_cast<const xcb_configure_notify_event_t *>(ev); + resize_swapchain(notify->width, notify->height); + } + break; + case XCB_KEY_PRESS: + { + const xcb_key_press_event_t *press = + reinterpret_cast<const xcb_key_press_event_t *>(ev); + Game::Key key; + + // TODO translate xcb_keycode_t + switch (press->detail) { + case 9: + key = Game::KEY_ESC; + break; + case 111: + key = Game::KEY_UP; + break; + case 116: + key = Game::KEY_DOWN; + break; + case 65: + key = Game::KEY_SPACE; + break; + default: + key = Game::KEY_UNKNOWN; + break; + } + + game_.on_key(key); + } + break; + case XCB_CLIENT_MESSAGE: + { + const xcb_client_message_event_t *msg = + reinterpret_cast<const xcb_client_message_event_t *>(ev); + if (msg->type == wm_protocols_ && msg->data.data32[0] == wm_delete_window_) + game_.on_key(Game::KEY_SHUTDOWN); + } + break; + default: + break; + } +} + +void ShellXcb::loop_wait() +{ + while (true) { + xcb_generic_event_t *ev = xcb_wait_for_event(c_); + if (!ev) + continue; + + handle_event(ev); + free(ev); + + if (quit_) + break; + + acquire_back_buffer(); + present_back_buffer(); + } +} + +void ShellXcb::loop_poll() +{ + PosixTimer timer; + + double current_time = timer.get(); + double profile_start_time = current_time; + int profile_present_count = 0; + + while (true) { + // handle pending events + while (true) { + xcb_generic_event_t *ev = xcb_poll_for_event(c_); + if (!ev) + break; + + handle_event(ev); + free(ev); + } + + if (quit_) + break; + + acquire_back_buffer(); + + double t = timer.get(); + add_game_time(static_cast<float>(t - current_time)); + + present_back_buffer(); + + current_time = t; + + profile_present_count++; + if (current_time - profile_start_time >= 5.0) { + const double fps = profile_present_count / (current_time - profile_start_time); + std::stringstream ss; + ss << profile_present_count << " presents in " << + current_time - profile_start_time << " seconds " << + "(FPS: " << fps << ")"; + log(LOG_INFO, ss.str().c_str()); + + profile_start_time = current_time; + profile_present_count = 0; + } + } +} + +void ShellXcb::run() +{ + create_window(); + xcb_map_window(c_, win_); + xcb_flush(c_); + + create_context(); + resize_swapchain(settings_.initial_width, settings_.initial_height); + + quit_ = false; + if (settings_.animate) + loop_poll(); + else + loop_wait(); + + destroy_context(); + + xcb_destroy_window(c_, win_); + xcb_flush(c_); +} |
