Fix infinite loop detection when placing players randomly on the newer random map...
[0ad.git] / source / ps / Util.cpp
blob1c499f660c487b0daa97a81033153f841f254068
1 /* Copyright (C) 2016 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "ps/Util.h"
22 #include "lib/posix/posix_utsname.h"
23 #include "lib/ogl.h"
24 #include "lib/timer.h"
25 #include "lib/bits.h" // round_up
26 #include "lib/allocators/shared_ptr.h"
27 #include "lib/sysdep/sysdep.h" // sys_OpenFile
28 #include "lib/sysdep/gfx.h"
29 #include "lib/sysdep/snd.h"
30 #include "lib/sysdep/cpu.h"
31 #include "lib/sysdep/os_cpu.h"
32 #if ARCH_X86_X64
33 #include "lib/sysdep/arch/x86_x64/topology.h"
34 #endif
35 #include "lib/sysdep/smbios.h"
36 #include "lib/tex/tex.h"
38 #include "i18n/L10n.h"
39 #include "lib/utf8.h"
41 #include "ps/GameSetup/Config.h"
42 #include "ps/GameSetup/GameSetup.h"
43 #include "ps/Game.h"
44 #include "ps/CLogger.h"
45 #include "ps/Filesystem.h"
46 #include "ps/VideoMode.h"
47 #include "renderer/Renderer.h"
48 #include "maths/MathUtil.h"
49 #include "graphics/GameView.h"
51 #include <iomanip>
52 #include <sstream>
54 extern CStrW g_CursorName;
56 static std::string SplitExts(const char *exts)
58 std::string str = exts;
59 std::string ret = "";
60 size_t idx = str.find_first_of(" ");
61 while(idx != std::string::npos)
63 if(idx >= str.length() - 1)
65 ret += str;
66 break;
69 ret += str.substr(0, idx);
70 ret += "\n";
71 str = str.substr(idx + 1);
72 idx = str.find_first_of(" ");
75 return ret;
79 void WriteSystemInfo()
81 TIMER(L"write_sys_info");
83 // get_cpu_info and gfx_detect already called during init - see call site
84 snd_detect();
86 struct utsname un;
87 uname(&un);
89 OsPath pathname = psLogDir()/"system_info.txt";
90 FILE* f = sys_OpenFile(pathname, "w");
91 if(!f)
92 return;
94 // current timestamp (redundant WRT OS timestamp, but that is not
95 // visible when people are posting this file's contents online)
97 wchar_t timestampBuf[100] = {'\0'};
98 time_t seconds;
99 time(&seconds);
100 struct tm* t = gmtime(&seconds);
101 const size_t charsWritten = wcsftime(timestampBuf, ARRAY_SIZE(timestampBuf), L"(generated %Y-%m-%d %H:%M:%S UTC)", t);
102 ENSURE(charsWritten != 0);
103 fprintf(f, "%ls\n\n", timestampBuf);
106 // OS
107 fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version);
109 // CPU
110 fprintf(f, "CPU : %s, %s", un.machine, cpu_IdentifierString());
111 #if ARCH_X86_X64
112 fprintf(f, " (%dx%dx%d)", (int)topology::NumPackages(), (int)topology::CoresPerPackage(), (int)topology::LogicalPerCore());
113 #endif
114 double cpuClock = os_cpu_ClockFrequency(); // query OS (may fail)
115 #if ARCH_X86_X64
116 if(cpuClock <= 0.0)
117 cpuClock = x86_x64::ClockFrequency(); // measure (takes a few ms)
118 #endif
119 if(cpuClock > 0.0)
121 if(cpuClock < 1e9)
122 fprintf(f, ", %.2f MHz\n", cpuClock*1e-6);
123 else
124 fprintf(f, ", %.2f GHz\n", cpuClock*1e-9);
126 else
127 fprintf(f, "\n");
129 // memory
130 fprintf(f, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable());
132 // graphics
133 const std::wstring cardName = gfx::CardName();
134 const std::wstring driverInfo = gfx::DriverInfo();
135 fprintf(f, "Graphics Card : %ls\n", cardName.c_str());
136 fprintf(f, "OpenGL Drivers : %s; %ls\n", glGetString(GL_VERSION), driverInfo.c_str());
137 fprintf(f, "Video Mode : %dx%d:%d\n", g_VideoMode.GetXRes(), g_VideoMode.GetYRes(), g_VideoMode.GetBPP());
139 // sound
140 fprintf(f, "Sound Card : %ls\n", snd_card);
141 fprintf(f, "Sound Drivers : %ls\n", snd_drv_ver);
143 // OpenGL extensions (write them last, since it's a lot of text)
144 const char* exts = ogl_ExtensionString();
145 if (!exts) exts = "{unknown}";
146 fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str());
148 // System Management BIOS (even more text than OpenGL extensions)
149 std::string smbios = SMBIOS::StringizeStructures(SMBIOS::GetStructures());
150 fprintf(f, "\nSMBIOS: \n%s\n", smbios.c_str());
152 fclose(f);
153 f = 0;
157 // not thread-safe!
158 static const wchar_t* HardcodedErrorString(int err)
160 static wchar_t description[200];
161 StatusDescription((Status)err, description, ARRAY_SIZE(description));
162 return description;
165 // not thread-safe!
166 const wchar_t* ErrorString(int err)
168 // language file not available (yet)
169 return HardcodedErrorString(err);
171 // TODO: load from language file
176 // write the specified texture to disk.
177 // note: <t> cannot be made const because the image may have to be
178 // transformed to write it out in the format determined by <fn>'s extension.
179 Status tex_write(Tex* t, const VfsPath& filename)
181 DynArray da;
182 RETURN_STATUS_IF_ERR(t->encode(filename.Extension(), &da));
184 // write to disk
185 Status ret = INFO::OK;
187 shared_ptr<u8> file = DummySharedPtr(da.base);
188 const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
189 if(bytes_written > 0)
190 ENSURE(bytes_written == (ssize_t)da.pos);
191 else
192 ret = (Status)bytes_written;
195 (void)da_free(&da);
196 return ret;
200 * Return an unused directory, based on date and index (for example 2016-02-09_0001)
202 OsPath createDateIndexSubdirectory(const OsPath& parentDir)
204 const std::time_t timestamp = std::time(nullptr);
205 const struct std::tm* now = std::localtime(&timestamp);
207 // Two processes executing this simultaneously might attempt to create the same directory.
208 int tries = 0;
209 const int maxTries = 10;
211 int i = 0;
212 OsPath path;
213 char directory[256];
217 sprintf(directory, "%04d-%02d-%02d_%04d", now->tm_year+1900, now->tm_mon+1, now->tm_mday, ++i);
218 path = parentDir / CStr(directory);
220 if (DirectoryExists(path) || FileExists(path))
221 continue;
223 if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK)
224 break;
226 } while(tries <= maxTries);
228 return path;
231 static size_t s_nextScreenshotNumber;
233 // <extension> identifies the file format that is to be written
234 // (case-insensitive). examples: "bmp", "png", "jpg".
235 // BMP is good for quick output at the expense of large files.
236 void WriteScreenshot(const VfsPath& extension)
238 // get next available numbered filename
239 // note: %04d -> always 4 digits, so sorting by filename works correctly.
240 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
241 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
242 VfsPath filename;
243 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
245 const size_t w = (size_t)g_xres, h = (size_t)g_yres;
246 const size_t bpp = 24;
247 GLenum fmt = GL_RGB;
248 int flags = TEX_BOTTOM_UP;
249 // we want writing BMP to be as fast as possible,
250 // so read data from OpenGL in BMP format to obviate conversion.
251 if(extension == L".bmp")
253 #if !CONFIG2_GLES // GLES doesn't support BGR
254 fmt = GL_BGR;
255 flags |= TEX_BGR;
256 #endif
259 // Hide log messages and re-render
260 RenderLogger(false);
261 Render();
262 RenderLogger(true);
264 const size_t img_size = w * h * bpp/8;
265 const size_t hdr_size = tex_hdr_size(filename);
266 shared_ptr<u8> buf;
267 AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
268 GLvoid* img = buf.get() + hdr_size;
269 Tex t;
270 if(t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
271 return;
272 glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
274 if (tex_write(&t, filename) == INFO::OK)
276 OsPath realPath;
277 g_VFS->GetRealPath(filename, realPath);
279 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
281 debug_printf(
282 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
283 realPath.string8().c_str());
285 else
286 LOGERROR("Error writing screenshot to '%s'", filename.string8());
291 // Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles.
292 void WriteBigScreenshot(const VfsPath& extension, int tiles)
294 // If the game hasn't started yet then use WriteScreenshot to generate the image.
295 if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; }
297 // get next available numbered filename
298 // note: %04d -> always 4 digits, so sorting by filename works correctly.
299 const VfsPath basenameFormat(L"screenshots/screenshot%04d");
300 const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
301 VfsPath filename;
302 vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
304 // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
305 // hope the screen is actually large enough for that.
306 const int tile_w = 640, tile_h = 480;
307 ENSURE(g_xres >= tile_w && g_yres >= tile_h);
309 const int img_w = tile_w*tiles, img_h = tile_h*tiles;
310 const int bpp = 24;
311 GLenum fmt = GL_RGB;
312 int flags = TEX_BOTTOM_UP;
313 // we want writing BMP to be as fast as possible,
314 // so read data from OpenGL in BMP format to obviate conversion.
315 if(extension == L".bmp")
317 #if !CONFIG2_GLES // GLES doesn't support BGR
318 fmt = GL_BGR;
319 flags |= TEX_BGR;
320 #endif
323 const size_t img_size = img_w * img_h * bpp/8;
324 const size_t tile_size = tile_w * tile_h * bpp/8;
325 const size_t hdr_size = tex_hdr_size(filename);
326 void* tile_data = malloc(tile_size);
327 if(!tile_data)
329 WARN_IF_ERR(ERR::NO_MEM);
330 return;
332 shared_ptr<u8> img_buf;
333 AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize);
335 Tex t;
336 GLvoid* img = img_buf.get() + hdr_size;
337 if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0)
339 free(tile_data);
340 return;
343 ogl_WarnIfError();
345 // Resize various things so that the sizes and aspect ratios are correct
347 g_Renderer.Resize(tile_w, tile_h);
348 SViewPort vp = { 0, 0, tile_w, tile_h };
349 g_Game->GetView()->GetCamera()->SetViewPort(vp);
350 g_Game->GetView()->SetCameraProjection();
353 #if !CONFIG2_GLES
354 // Temporarily move everything onto the front buffer, so the user can
355 // see the exciting progress as it renders (and can tell when it's finished).
356 // (It doesn't just use SwapBuffers, because it doesn't know whether to
357 // call the SDL version or the Atlas version.)
358 GLint oldReadBuffer, oldDrawBuffer;
359 glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer);
360 glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer);
361 glDrawBuffer(GL_FRONT);
362 glReadBuffer(GL_FRONT);
363 #endif
365 // Hide the cursor
366 CStrW oldCursor = g_CursorName;
367 g_CursorName = L"";
369 // Render each tile
370 for (int tile_y = 0; tile_y < tiles; ++tile_y)
372 for (int tile_x = 0; tile_x < tiles; ++tile_x)
374 // Adjust the camera to render the appropriate region
375 g_Game->GetView()->GetCamera()->SetProjectionTile(tiles, tile_x, tile_y);
377 RenderLogger(false);
378 RenderGui(false);
379 Render();
380 RenderGui(true);
381 RenderLogger(true);
383 // Copy the tile pixels into the main image
384 glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data);
385 for (int y = 0; y < tile_h; ++y)
387 void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8;
388 void* src = (char*)tile_data + y * tile_w * bpp/8;
389 memcpy(dest, src, tile_w * bpp/8);
394 // Restore the old cursor
395 g_CursorName = oldCursor;
397 #if !CONFIG2_GLES
398 // Restore the buffer settings
399 glDrawBuffer(oldDrawBuffer);
400 glReadBuffer(oldReadBuffer);
401 #endif
403 // Restore the viewport settings
405 g_Renderer.Resize(g_xres, g_yres);
406 SViewPort vp = { 0, 0, g_xres, g_yres };
407 g_Game->GetView()->GetCamera()->SetViewPort(vp);
408 g_Game->GetView()->SetCameraProjection();
409 g_Game->GetView()->GetCamera()->SetProjectionTile(1, 0, 0);
412 if (tex_write(&t, filename) == INFO::OK)
414 OsPath realPath;
415 g_VFS->GetRealPath(filename, realPath);
417 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
419 debug_printf(
420 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
421 realPath.string8().c_str());
423 else
424 LOGERROR("Error writing screenshot to '%s'", filename.string8());
426 free(tile_data);
429 std::string Hexify(const std::string& s)
431 std::stringstream str;
432 str << std::hex;
433 for (const char& c : s)
434 str << std::setfill('0') << std::setw(2) << (int)(unsigned char)c;
435 return str.str();