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_, ®istry_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