• 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 <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 
28 /* Unused attribute / variable MACRO.
29    Some methods of classes' heirs do not need all fuction parameters.
30    This triggers warning on GCC platfoms. This macro will silence them.
31 */
32 #if defined(__GNUC__)
33 #define UNUSED __attribute__((unused))
34 #else
35 #define UNUSED
36 #endif
37 
38 namespace {
39 
40 class PosixTimer {
41   public:
PosixTimer()42     PosixTimer() { reset(); }
43 
reset()44     void reset() { clock_gettime(CLOCK_MONOTONIC, &start_); }
45 
get() const46     double get() const {
47         struct timespec now;
48         clock_gettime(CLOCK_MONOTONIC, &now);
49 
50         constexpr long one_s_in_ns = 1000 * 1000 * 1000;
51         constexpr double one_s_in_ns_d = static_cast<double>(one_s_in_ns);
52 
53         time_t s = now.tv_sec - start_.tv_sec;
54         long ns;
55         if (now.tv_nsec > start_.tv_nsec) {
56             ns = now.tv_nsec - start_.tv_nsec;
57         } else {
58             assert(s > 0);
59             s--;
60             ns = one_s_in_ns - (start_.tv_nsec - now.tv_nsec);
61         }
62 
63         return static_cast<double>(s) + static_cast<double>(ns) / one_s_in_ns_d;
64     }
65 
66   private:
67     struct timespec start_;
68 };
69 
70 } // namespace
71 
72 const struct wl_registry_listener ShellWayland::registry_listener_ = {
73     ShellWayland::handle_global, ShellWayland::handle_global_remove};
74 
75 const struct wl_shell_surface_listener ShellWayland::shell_surface_listener_ = {
76     ShellWayland::handle_ping, ShellWayland::handle_configure,
77     ShellWayland::handle_popup_done};
78 
handle_global(void * data,struct wl_registry * registry,uint32_t id,const char * interface,uint32_t version UNUSED)79 void ShellWayland::handle_global(void *data, struct wl_registry *registry,
80                                  uint32_t id, const char *interface,
81                                  uint32_t version UNUSED) {
82     ShellWayland *_this = static_cast<ShellWayland *>(data);
83 
84     if (!strcmp(interface, "wl_compositor"))
85         _this->compositor_ = static_cast<struct wl_compositor *>(
86             wl_registry_bind(registry, id, &wl_compositor_interface, 3));
87     /* Todo: When xdg_shell protocol has stablized, we should move wl_shell tp
88      * xdg_shell */
89     else if (!strcmp(interface, "wl_shell"))
90         _this->shell_ = static_cast<struct wl_shell *>(
91             wl_registry_bind(registry, id, &wl_shell_interface, 1));
92 }
93 
handle_global_remove(void * data UNUSED,struct wl_registry * registry UNUSED,uint32_t name UNUSED)94 void ShellWayland::handle_global_remove(void *data UNUSED,
95                                         struct wl_registry *registry UNUSED,
96                                         uint32_t name UNUSED) {}
97 
handle_ping(void * data UNUSED,struct wl_shell_surface * shell_surface,uint32_t serial)98 void ShellWayland::handle_ping(void *data UNUSED,
99                                struct wl_shell_surface *shell_surface,
100                                uint32_t serial) {
101     wl_shell_surface_pong(shell_surface, serial);
102 }
103 
handle_configure(void * data UNUSED,struct wl_shell_surface * shell_surface UNUSED,uint32_t edges UNUSED,int32_t width UNUSED,int32_t height UNUSED)104 void ShellWayland::handle_configure(
105     void *data UNUSED, struct wl_shell_surface *shell_surface UNUSED,
106     uint32_t edges UNUSED, int32_t width UNUSED, int32_t height UNUSED) {}
107 
handle_popup_done(void * data UNUSED,struct wl_shell_surface * shell_surface UNUSED)108 void ShellWayland::handle_popup_done(
109     void *data UNUSED, struct wl_shell_surface *shell_surface UNUSED) {}
110 
ShellWayland(Game & game)111 ShellWayland::ShellWayland(Game &game) : Shell(game) {
112     instance_extensions_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
113 
114     init_connection();
115     init_vk();
116 }
117 
~ShellWayland()118 ShellWayland::~ShellWayland() {
119     cleanup_vk();
120     dlclose(lib_handle_);
121 
122     if (shell_surface_)
123         wl_shell_surface_destroy(shell_surface_);
124     if (surface_)
125         wl_surface_destroy(surface_);
126     if (shell_)
127         wl_shell_destroy(shell_);
128     if (compositor_)
129         wl_compositor_destroy(compositor_);
130     if (registry_)
131         wl_registry_destroy(registry_);
132     if (display_)
133         wl_display_disconnect(display_);
134 }
135 
init_connection()136 void ShellWayland::init_connection() {
137     try {
138         display_ = wl_display_connect(NULL);
139         if (!display_)
140             throw std::runtime_error("failed to connect to the display server");
141 
142         registry_ = wl_display_get_registry(display_);
143         if (!registry_)
144             throw std::runtime_error("failed to get registry");
145 
146         wl_registry_add_listener(registry_, &registry_listener_, this);
147         wl_display_roundtrip(display_);
148 
149         if (!compositor_)
150             throw std::runtime_error("failed to bind compositor");
151 
152         if (!shell_)
153             throw std::runtime_error("failed to bind shell");
154     } catch (...) {
155         if (shell_)
156             wl_shell_destroy(shell_);
157         if (compositor_)
158             wl_compositor_destroy(compositor_);
159         if (registry_)
160             wl_registry_destroy(registry_);
161         if (display_)
162             wl_display_disconnect(display_);
163 
164         throw;
165     }
166 }
167 
create_window()168 void ShellWayland::create_window() {
169     surface_ = wl_compositor_create_surface(compositor_);
170     if (!surface_)
171         throw std::runtime_error("failed to create surface");
172 
173     shell_surface_ = wl_shell_get_shell_surface(shell_, surface_);
174     if (!shell_surface_)
175         throw std::runtime_error("failed to shell_surface");
176 
177     wl_shell_surface_add_listener(shell_surface_, &shell_surface_listener_,
178                                   this);
179     // set title
180     wl_shell_surface_set_title(shell_surface_, settings_.name.c_str());
181     wl_shell_surface_set_toplevel(shell_surface_);
182 }
183 
load_vk()184 PFN_vkGetInstanceProcAddr ShellWayland::load_vk() {
185     const char filename[] = "libvulkan.so";
186     void *handle, *symbol;
187 
188 #ifdef UNINSTALLED_LOADER
189     handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY);
190     if (!handle)
191         handle = dlopen(filename, RTLD_LAZY);
192 #else
193     handle = dlopen(filename, RTLD_LAZY);
194 #endif
195 
196     if (handle)
197         symbol = dlsym(handle, "vkGetInstanceProcAddr");
198 
199     if (!handle || !symbol) {
200         std::stringstream ss;
201         ss << "failed to load " << dlerror();
202 
203         if (handle)
204             dlclose(handle);
205 
206         throw std::runtime_error(ss.str());
207     }
208 
209     lib_handle_ = handle;
210 
211     return reinterpret_cast<PFN_vkGetInstanceProcAddr>(symbol);
212 }
213 
can_present(VkPhysicalDevice phy,uint32_t queue_family)214 bool ShellWayland::can_present(VkPhysicalDevice phy, uint32_t queue_family) {
215     return vk::GetPhysicalDeviceWaylandPresentationSupportKHR(phy, queue_family,
216                                                               display_);
217 }
218 
create_surface(VkInstance instance)219 VkSurfaceKHR ShellWayland::create_surface(VkInstance instance) {
220     VkWaylandSurfaceCreateInfoKHR surface_info = {};
221     surface_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
222     surface_info.display = display_;
223     surface_info.surface = surface_;
224 
225     VkSurfaceKHR surface;
226     vk::assert_success(vk::CreateWaylandSurfaceKHR(instance, &surface_info,
227                                                    nullptr, &surface));
228 
229     return surface;
230 }
231 
loop_wait()232 void ShellWayland::loop_wait() {
233     while (true) {
234         if (quit_)
235             break;
236 
237         acquire_back_buffer();
238         present_back_buffer();
239     }
240 }
241 
loop_poll()242 void ShellWayland::loop_poll() {
243     PosixTimer timer;
244 
245     double current_time = timer.get();
246     double profile_start_time = current_time;
247     int profile_present_count = 0;
248 
249     while (true) {
250         if (quit_)
251             break;
252 
253         acquire_back_buffer();
254 
255         double t = timer.get();
256         add_game_time(static_cast<float>(t - current_time));
257 
258         present_back_buffer();
259 
260         current_time = t;
261 
262         profile_present_count++;
263         if (current_time - profile_start_time >= 5.0) {
264             const double fps =
265                 profile_present_count / (current_time - profile_start_time);
266             std::stringstream ss;
267             ss << profile_present_count << " presents in "
268                << current_time - profile_start_time << " seconds "
269                << "(FPS: " << fps << ")";
270             log(LOG_INFO, ss.str().c_str());
271 
272             profile_start_time = current_time;
273             profile_present_count = 0;
274         }
275     }
276 }
277 
run()278 void ShellWayland::run() {
279     create_window();
280     create_context();
281     resize_swapchain(settings_.initial_width, settings_.initial_height);
282 
283     quit_ = false;
284     if (settings_.animate)
285         loop_poll();
286     else
287         loop_wait();
288 
289     destroy_context();
290 }
291