1 /*
2 Copyright (c) 2005-2022 Intel Corporation
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 /////// Common internal implementation of Windows-specific stuff //////////////
18 /////// Must be the first included header //////////////
19
20 #ifndef __WINVIDEO_H__
21 #define __WINVIDEO_H__
22
23 #ifndef _CRT_SECURE_NO_DEPRECATE
24 #define _CRT_SECURE_NO_DEPRECATE
25 #endif
26 // Check that the target Windows version has all API calls required.
27 #ifndef _WIN32_WINNT
28 #define _WIN32_WINNT 0x0400
29 #endif
30 #if _WIN32_WINNT < 0x0400
31 #define YIELD_TO_THREAD() Sleep(0)
32 #else
33 #define YIELD_TO_THREAD() SwitchToThread()
34 #endif
35 #include "video.hpp"
36 #include <fcntl.h>
37 #include <io.h>
38 #include <iostream>
39 #include <fstream>
40
41 #pragma comment(lib, "gdi32.lib")
42 #pragma comment(lib, "user32.lib")
43
44 // maximum number of lines the output console should have
45 static const WORD MAX_CONSOLE_LINES = 500;
46 const COLORREF RGBKEY = RGB(8, 8, 16); // at least 8 for 16-bit palette
47 HWND g_hAppWnd; // The program's window handle
48 HANDLE g_handles[2] = { 0, 0 }; // thread and wake up event
49 unsigned int *g_pImg = 0; // drawing memory
50 int g_sizex, g_sizey;
51 static video *g_video = 0;
52 WNDPROC g_pUserProc = 0;
53 HINSTANCE video::win_hInstance = 0;
54 int video::win_iCmdShow = 0;
55 static WNDCLASSEX *gWndClass = 0;
56 static HACCEL hAccelTable = 0;
57 static DWORD g_msec = 0;
58 static int g_fps = 0, g_updates = 0, g_skips = 0;
59
60 bool DisplayError(LPSTR lpstrErr, HRESULT hres = 0); // always returns false
61 LRESULT CALLBACK InternalWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
62
63 //! Create window
WinInit(HINSTANCE hInstance,int nCmdShow,WNDCLASSEX * uwc,const char * title,bool fixedsize)64 bool WinInit(HINSTANCE hInstance,
65 int nCmdShow,
66 WNDCLASSEX *uwc,
67 const char *title,
68 bool fixedsize) {
69 WNDCLASSEX wndclass; // Our app's windows class
70 if (uwc) {
71 memcpy(&wndclass, uwc, sizeof(wndclass));
72 g_pUserProc = uwc->lpfnWndProc;
73 }
74 else {
75 memset(&wndclass, 0, sizeof(wndclass));
76 wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
77 wndclass.lpszClassName = title;
78 }
79 wndclass.cbSize = sizeof(wndclass);
80 wndclass.hInstance = hInstance;
81 wndclass.lpfnWndProc = InternalWndProc;
82 wndclass.style |= CS_HREDRAW | CS_VREDRAW;
83 wndclass.hbrBackground = CreateSolidBrush(RGBKEY);
84
85 if (!RegisterClassExA(&wndclass))
86 return false;
87 int xaddend = GetSystemMetrics(fixedsize ? SM_CXFIXEDFRAME : SM_CXFRAME) * 2;
88 int yaddend = GetSystemMetrics(fixedsize ? SM_CYFIXEDFRAME : SM_CYFRAME) * 2 +
89 GetSystemMetrics(SM_CYCAPTION);
90 if (wndclass.lpszMenuName)
91 yaddend += GetSystemMetrics(SM_CYMENU);
92
93 // Setup the new window's physical parameters - and tell Windows to create it
94 g_hAppWnd = CreateWindowA(wndclass.lpszClassName, // Window class name
95 title, // Window caption
96 !fixedsize ? WS_OVERLAPPEDWINDOW : // Window style
97 WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
98 CW_USEDEFAULT, // Initial x pos: use default placement
99 0, // Initial y pos: not used here
100 g_sizex + xaddend, // Initial x size
101 g_sizey + yaddend, // Initial y size
102 nullptr, // parent window handle
103 nullptr, // window menu handle
104 hInstance, // program instance handle
105 nullptr); // Creation parameters
106 return g_hAppWnd != nullptr;
107 }
108
109 //! create console window with redirection
RedirectIOToConsole(void)110 static bool RedirectIOToConsole(void) {
111 int hConHandle;
112 size_t lStdHandle;
113 CONSOLE_SCREEN_BUFFER_INFO coninfo;
114 FILE *fp;
115 // allocate a console for this app
116 AllocConsole();
117
118 // set the screen buffer to be big enough to let us scroll text
119 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
120 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
121 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
122
123 // redirect unbuffered STDOUT to the console
124 lStdHandle = (size_t)GetStdHandle(STD_OUTPUT_HANDLE);
125 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
126 if (hConHandle <= 0)
127 return false;
128 fp = _fdopen(hConHandle, "w");
129 *stdout = *fp;
130 setvbuf(stdout, nullptr, _IONBF, 0);
131
132 // redirect unbuffered STDERR to the console
133 lStdHandle = (size_t)GetStdHandle(STD_ERROR_HANDLE);
134 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
135 if (hConHandle > 0) {
136 fp = _fdopen(hConHandle, "w");
137 *stderr = *fp;
138 setvbuf(stderr, nullptr, _IONBF, 0);
139 }
140
141 // redirect unbuffered STDIN to the console
142 lStdHandle = (size_t)GetStdHandle(STD_INPUT_HANDLE);
143 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
144 if (hConHandle > 0) {
145 fp = _fdopen(hConHandle, "r");
146 *stdin = *fp;
147 setvbuf(stdin, nullptr, _IONBF, 0);
148 }
149
150 // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
151 // point to console as well
152 std::ios::sync_with_stdio();
153 return true;
154 }
155
video()156 video::video()
157 : depth(24),
158 red_shift(16),
159 green_shift(8),
160 blue_shift(0),
161 red_mask(0xff0000),
162 green_mask(0xff00),
163 blue_mask(0xff) {
164 assert(g_video == 0);
165 g_video = this;
166 title = "Video";
167 running = threaded = calc_fps = false;
168 updating = true;
169 }
170
171 //! optionally call it just before init() to set own
win_set_class(WNDCLASSEX & wcex)172 void video::win_set_class(WNDCLASSEX &wcex) {
173 gWndClass = &wcex;
174 }
175
win_load_accelerators(int idc)176 void video::win_load_accelerators(int idc) {
177 hAccelTable = LoadAccelerators(win_hInstance, MAKEINTRESOURCE(idc));
178 }
179
init_console()180 bool video::init_console() {
181 if (RedirectIOToConsole()) {
182 if (!g_pImg && g_sizex && g_sizey)
183 g_pImg = new unsigned int[g_sizex * g_sizey];
184 if (g_pImg)
185 running = true;
186 return true;
187 }
188 return false;
189 }
190
~video()191 video::~video() {
192 if (g_video)
193 terminate();
194 }
195
thread_video(LPVOID lpParameter)196 DWORD WINAPI thread_video(LPVOID lpParameter) {
197 video *v = (video *)lpParameter;
198 v->on_process();
199 return 0;
200 }
201
loop_once(video * v)202 static bool loop_once(video *v) {
203 // screen update notify
204 if (int updates = g_updates) {
205 g_updates = 0;
206 if (g_video->updating) {
207 g_skips += updates - 1;
208 g_fps++;
209 }
210 else
211 g_skips += updates;
212 UpdateWindow(g_hAppWnd);
213 }
214 // update fps
215 DWORD msec = GetTickCount();
216 if (v->calc_fps && msec >= g_msec + 1000) {
217 double sec = (msec - g_msec) / 1000.0;
218 char buffer[256],
219 n = _snprintf(buffer, 128, "%s: %d fps", v->title, int(double(g_fps + g_skips) / sec));
220 if (g_skips)
221 _snprintf(buffer + n,
222 128,
223 " - %d skipped = %d updates",
224 int(g_skips / sec),
225 int(g_fps / sec));
226 SetWindowTextA(g_hAppWnd, buffer);
227 g_msec = msec;
228 g_skips = g_fps = 0;
229 }
230 // event processing, including painting
231 MSG msg;
232 if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
233 if (msg.message == WM_QUIT) {
234 v->running = false;
235 return false;
236 }
237 if (!hAccelTable || !TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
238 TranslateMessage(&msg);
239 DispatchMessage(&msg);
240 }
241 return true; // try again
242 }
243 return false;
244 }
245
246 //! Do standard event loop
main_loop()247 void video::main_loop() {
248 // let Windows draw and unroll the window
249 InvalidateRect(g_hAppWnd, 0, false);
250 g_msec = GetTickCount(); // let's stay for 0,5 sec
251 while (g_msec + 500 > GetTickCount()) {
252 loop_once(this);
253 Sleep(1);
254 }
255 g_msec = GetTickCount();
256 // now, start main process
257 if (threaded) {
258 g_handles[0] = CreateThread(nullptr, // LPSECURITY_ATTRIBUTES security_attrs
259 0, // SIZE_T stacksize
260 (LPTHREAD_START_ROUTINE)thread_video,
261 this, // argument
262 0,
263 0);
264 if (!g_handles[0]) {
265 DisplayError("Can't create thread");
266 return;
267 }
268 else // harmless race is possible here
269 g_handles[1] = CreateEvent(nullptr, false, false, nullptr);
270 while (running) {
271 while (loop_once(this))
272 ;
273 YIELD_TO_THREAD(); // give time for processing when running on single CPU
274 DWORD r = MsgWaitForMultipleObjects(
275 2, g_handles, false, INFINITE, QS_ALLINPUT ^ QS_MOUSEMOVE);
276 if (r == WAIT_OBJECT_0)
277 break; // thread terminated
278 }
279 running = false;
280 if (WaitForSingleObject(g_handles[0], 3000) == WAIT_TIMEOUT) {
281 // there was not enough time for graceful shutdown, killing the example with code 1.
282 exit(1);
283 }
284 if (g_handles[0])
285 CloseHandle(g_handles[0]);
286 if (g_handles[1])
287 CloseHandle(g_handles[1]);
288 g_handles[0] = g_handles[1] = 0;
289 }
290 else
291 on_process();
292 }
293
294 //! Refresh screen picture
next_frame()295 bool video::next_frame() {
296 if (!running)
297 return false;
298 g_updates++; // Fast but inaccurate counter. The data race here is benign.
299 if (!threaded)
300 while (loop_once(this))
301 ;
302 else if (g_handles[1]) {
303 SetEvent(g_handles[1]);
304 YIELD_TO_THREAD();
305 }
306 return true;
307 }
308
309 //! Change window title
show_title()310 void video::show_title() {
311 if (g_hAppWnd)
312 SetWindowTextA(g_hAppWnd, title);
313 }
314
315 #endif //__WINVIDEO_H__
316