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"
22 #include "lib/posix/posix_utsname.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"
33 #include "lib/sysdep/arch/x86_x64/topology.h"
35 #include "lib/sysdep/smbios.h"
36 #include "lib/tex/tex.h"
38 #include "i18n/L10n.h"
41 #include "ps/GameSetup/Config.h"
42 #include "ps/GameSetup/GameSetup.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"
54 extern CStrW g_CursorName
;
56 static std::string
SplitExts(const char *exts
)
58 std::string str
= exts
;
60 size_t idx
= str
.find_first_of(" ");
61 while(idx
!= std::string::npos
)
63 if(idx
>= str
.length() - 1)
69 ret
+= str
.substr(0, idx
);
71 str
= str
.substr(idx
+ 1);
72 idx
= str
.find_first_of(" ");
79 void WriteSystemInfo()
81 TIMER(L
"write_sys_info");
83 // get_cpu_info and gfx_detect already called during init - see call site
89 OsPath pathname
= psLogDir()/"system_info.txt";
90 FILE* f
= sys_OpenFile(pathname
, "w");
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'};
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
);
107 fprintf(f
, "OS : %s %s (%s)\n", un
.sysname
, un
.release
, un
.version
);
110 fprintf(f
, "CPU : %s, %s", un
.machine
, cpu_IdentifierString());
112 fprintf(f
, " (%dx%dx%d)", (int)topology::NumPackages(), (int)topology::CoresPerPackage(), (int)topology::LogicalPerCore());
114 double cpuClock
= os_cpu_ClockFrequency(); // query OS (may fail)
117 cpuClock
= x86_x64::ClockFrequency(); // measure (takes a few ms)
122 fprintf(f
, ", %.2f MHz\n", cpuClock
*1e-6);
124 fprintf(f
, ", %.2f GHz\n", cpuClock
*1e-9);
130 fprintf(f
, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable());
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());
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());
158 static const wchar_t* HardcodedErrorString(int err
)
160 static wchar_t description
[200];
161 StatusDescription((Status
)err
, description
, ARRAY_SIZE(description
));
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
)
182 RETURN_STATUS_IF_ERR(t
->encode(filename
.Extension(), &da
));
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
);
192 ret
= (Status
)bytes_written
;
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(×tamp
);
207 // Two processes executing this simultaneously might attempt to create the same directory.
209 const int maxTries
= 10;
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
))
223 if (CreateDirectories(path
, 0700, ++tries
> maxTries
) == INFO::OK
)
226 } while(tries
<= maxTries
);
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
);
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;
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
259 // Hide log messages and re-render
264 const size_t img_size
= w
* h
* bpp
/8;
265 const size_t hdr_size
= tex_hdr_size(filename
);
267 AllocateAligned(buf
, hdr_size
+img_size
, maxSectorSize
);
268 GLvoid
* img
= buf
.get() + hdr_size
;
270 if(t
.wrap(w
, h
, bpp
, flags
, buf
, hdr_size
) < 0)
272 glReadPixels(0, 0, (GLsizei
)w
, (GLsizei
)h
, fmt
, GL_UNSIGNED_BYTE
, img
);
274 if (tex_write(&t
, filename
) == INFO::OK
)
277 g_VFS
->GetRealPath(filename
, realPath
);
279 LOGMESSAGERENDER(g_L10n
.Translate("Screenshot written to '%s'"), realPath
.string8());
282 CStr(g_L10n
.Translate("Screenshot written to '%s'") + "\n").c_str(),
283 realPath
.string8().c_str());
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
);
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
;
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
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
);
329 WARN_IF_ERR(ERR::NO_MEM
);
332 shared_ptr
<u8
> img_buf
;
333 AllocateAligned(img_buf
, hdr_size
+img_size
, maxSectorSize
);
336 GLvoid
* img
= img_buf
.get() + hdr_size
;
337 if(t
.wrap(img_w
, img_h
, bpp
, flags
, img_buf
, hdr_size
) < 0)
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();
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
);
366 CStrW oldCursor
= g_CursorName
;
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
);
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
;
398 // Restore the buffer settings
399 glDrawBuffer(oldDrawBuffer
);
400 glReadBuffer(oldReadBuffer
);
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
)
415 g_VFS
->GetRealPath(filename
, realPath
);
417 LOGMESSAGERENDER(g_L10n
.Translate("Screenshot written to '%s'"), realPath
.string8());
420 CStr(g_L10n
.Translate("Screenshot written to '%s'") + "\n").c_str(),
421 realPath
.string8().c_str());
424 LOGERROR("Error writing screenshot to '%s'", filename
.string8());
429 std::string
Hexify(const std::string
& s
)
431 std::stringstream str
;
433 for (const char& c
: s
)
434 str
<< std::setfill('0') << std::setw(2) << (int)(unsigned char)c
;