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 <iostream>
25 #include <sstream>
26 
27 #include "Helpers.h"
28 #include "Game.h"
29 #include "ShellWin32.h"
30 
31 namespace {
32 
33 class Win32Timer {
34 public:
Win32Timer()35     Win32Timer()
36     {
37         LARGE_INTEGER freq;
38         QueryPerformanceFrequency(&freq);
39         freq_ = static_cast<double>(freq.QuadPart);
40 
41         reset();
42     }
43 
reset()44     void reset()
45     {
46         QueryPerformanceCounter(&start_);
47     }
48 
get() const49     double get() const
50     {
51         LARGE_INTEGER now;
52         QueryPerformanceCounter(&now);
53 
54         return static_cast<double>(now.QuadPart - start_.QuadPart) / freq_;
55     }
56 
57 private:
58     double freq_;
59     LARGE_INTEGER start_;
60 };
61 
62 } // namespace
63 
ShellWin32(Game & game)64 ShellWin32::ShellWin32(Game &game) : Shell(game), hwnd_(nullptr)
65 {
66     instance_extensions_.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
67     init_vk();
68 }
69 
~ShellWin32()70 ShellWin32::~ShellWin32()
71 {
72     cleanup_vk();
73     FreeLibrary(hmodule_);
74 }
75 
create_window()76 void ShellWin32::create_window()
77 {
78     const std::string class_name(settings_.name + "WindowClass");
79 
80     hinstance_ = GetModuleHandle(nullptr);
81 
82     WNDCLASSEX win_class = {};
83     win_class.cbSize = sizeof(WNDCLASSEX);
84     win_class.style = CS_HREDRAW | CS_VREDRAW;
85     win_class.lpfnWndProc = window_proc;
86     win_class.hInstance = hinstance_;
87     win_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
88     win_class.lpszClassName = class_name.c_str();
89     RegisterClassEx(&win_class);
90 
91     const DWORD win_style =
92         WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE | WS_OVERLAPPEDWINDOW;
93 
94     RECT win_rect = { 0, 0, settings_.initial_width, settings_.initial_height };
95     AdjustWindowRect(&win_rect, win_style, false);
96 
97     hwnd_ = CreateWindowEx(WS_EX_APPWINDOW,
98                            class_name.c_str(),
99                            settings_.name.c_str(),
100                            win_style,
101                            0,
102                            0,
103                            win_rect.right - win_rect.left,
104                            win_rect.bottom - win_rect.top,
105                            nullptr,
106                            nullptr,
107                            hinstance_,
108                            nullptr);
109 
110     SetForegroundWindow(hwnd_);
111     SetWindowLongPtr(hwnd_, GWLP_USERDATA, (LONG_PTR) this);
112 }
113 
load_vk()114 PFN_vkGetInstanceProcAddr ShellWin32::load_vk()
115 {
116     const char filename[] = "vulkan-1.dll";
117     HMODULE mod;
118     PFN_vkGetInstanceProcAddr get_proc;
119 
120     mod = LoadLibrary(filename);
121     if (mod) {
122         get_proc = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(
123                     mod, "vkGetInstanceProcAddr"));
124     }
125 
126     if (!mod || !get_proc) {
127         std::stringstream ss;
128         ss << "failed to load " << filename;
129 
130         if (mod)
131             FreeLibrary(mod);
132 
133         throw std::runtime_error(ss.str());
134     }
135 
136     hmodule_ = mod;
137 
138     return get_proc;
139 }
140 
can_present(VkPhysicalDevice phy,uint32_t queue_family)141 bool ShellWin32::can_present(VkPhysicalDevice phy, uint32_t queue_family)
142 {
143     return vk::GetPhysicalDeviceWin32PresentationSupportKHR(phy, queue_family);
144 }
145 
create_surface(VkInstance instance)146 VkSurfaceKHR ShellWin32::create_surface(VkInstance instance)
147 {
148     VkWin32SurfaceCreateInfoKHR surface_info = {};
149     surface_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
150     surface_info.hinstance = hinstance_;
151     surface_info.hwnd = hwnd_;
152 
153     VkSurfaceKHR surface;
154     vk::assert_success(vk::CreateWin32SurfaceKHR(instance, &surface_info, nullptr, &surface));
155 
156     return surface;
157 }
158 
handle_message(UINT msg,WPARAM wparam,LPARAM lparam)159 LRESULT ShellWin32::handle_message(UINT msg, WPARAM wparam, LPARAM lparam)
160 {
161     switch (msg) {
162     case WM_SIZE:
163         {
164             UINT w = LOWORD(lparam);
165             UINT h = HIWORD(lparam);
166             resize_swapchain(w, h);
167         }
168         break;
169     case WM_KEYDOWN:
170         {
171             Game::Key key;
172 
173             switch (wparam) {
174             case VK_ESCAPE:
175                 key = Game::KEY_ESC;
176                 break;
177             case VK_UP:
178                 key = Game::KEY_UP;
179                 break;
180             case VK_DOWN:
181                 key = Game::KEY_DOWN;
182                 break;
183             case VK_SPACE:
184                 key = Game::KEY_SPACE;
185                 break;
186             default:
187                 key = Game::KEY_UNKNOWN;
188                 break;
189             }
190 
191             game_.on_key(key);
192         }
193         break;
194     case WM_CLOSE:
195         game_.on_key(Game::KEY_SHUTDOWN);
196         break;
197     case WM_DESTROY:
198         quit();
199         break;
200     default:
201         return DefWindowProc(hwnd_, msg, wparam, lparam);
202         break;
203     }
204 
205     return 0;
206 }
207 
quit()208 void ShellWin32::quit()
209 {
210     PostQuitMessage(0);
211 }
212 
run()213 void ShellWin32::run()
214 {
215     create_window();
216 
217     create_context();
218     resize_swapchain(settings_.initial_width, settings_.initial_height);
219 
220     Win32Timer timer;
221     double current_time = timer.get();
222 
223     while (true) {
224         bool quit = false;
225 
226         assert(settings_.animate);
227 
228         // process all messages
229         MSG msg;
230         while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
231             if (msg.message == WM_QUIT) {
232                 quit = true;
233                 break;
234             }
235 
236             TranslateMessage(&msg);
237             DispatchMessage(&msg);
238         }
239 
240         if (quit)
241             break;
242 
243         acquire_back_buffer();
244 
245         double t = timer.get();
246         add_game_time(static_cast<float>(t - current_time));
247 
248         present_back_buffer();
249 
250         current_time = t;
251     }
252 
253     destroy_context();
254 
255     DestroyWindow(hwnd_);
256 }
257