1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Debug/BsDebug.h"
4#include "Debug/BsLog.h"
5#include "Error/BsException.h"
6#include "Debug/BsBitmapWriter.h"
7#include "FileSystem/BsFileSystem.h"
8#include "FileSystem/BsDataStream.h"
9#include "Utility/BsTime.h"
10
11#if BS_PLATFORM == BS_PLATFORM_WIN32 && BS_COMPILER == BS_COMPILER_MSVC
12#include <windows.h>
13#include <iostream>
14
15void logToIDEConsole(const bs::String& message, const char* channel)
16{
17 static bs::Mutex mutex;
18
19 bs::Lock lock(mutex);
20 OutputDebugString("[");
21 OutputDebugString(channel);
22 OutputDebugString("] ");
23 OutputDebugString(message.c_str());
24 OutputDebugString("\n");
25
26 // Also default output in case we're running without debugger attached
27 std::cout << "[" << channel << "] " << message << std::endl;
28}
29#else
30void logToIDEConsole(const bs::String& message, const char* channel)
31{
32 std::cout << "[" << channel << "] " << message << std::endl;
33}
34#endif
35
36namespace bs
37{
38 void Debug::logDebug(const String& msg)
39 {
40 mLog.logMsg(msg, (UINT32)DebugChannel::Debug);
41 logToIDEConsole(msg, "DEBUG");
42 }
43
44 void Debug::logWarning(const String& msg)
45 {
46 mLog.logMsg(msg, (UINT32)DebugChannel::Warning);
47 logToIDEConsole(msg, "WARNING");
48 }
49
50 void Debug::logError(const String& msg)
51 {
52 mLog.logMsg(msg, (UINT32)DebugChannel::Error);
53 logToIDEConsole(msg, "ERROR");
54 }
55
56 void Debug::log(const String& msg, UINT32 channel)
57 {
58 mLog.logMsg(msg, channel);
59 if (channel == (UINT32)DebugChannel::Debug)
60 logToIDEConsole(msg, "DEBUG");
61 if (channel == (UINT32)DebugChannel::Warning || channel == (UINT32)DebugChannel::CompilerWarning)
62 logToIDEConsole(msg, "WARNING");
63 if (channel == (UINT32)DebugChannel::Error || channel == (UINT32)DebugChannel::CompilerError)
64 logToIDEConsole(msg, "ERROR");
65 }
66
67 void Debug::writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath,
68 bool overwrite) const
69 {
70 if(FileSystem::isFile(filePath))
71 {
72 if(overwrite)
73 FileSystem::remove(filePath);
74 else
75 BS_EXCEPT(FileNotFoundException, "File already exists at specified location: " + filePath.toString());
76 }
77
78 SPtr<DataStream> ds = FileSystem::createAndOpenFile(filePath);
79
80 UINT32 bmpDataSize = BitmapWriter::getBMPSize(width, height, bytesPerPixel);
81 UINT8* bmpBuffer = bs_newN<UINT8>(bmpDataSize);
82
83 BitmapWriter::rawPixelsToBMP(rawPixels, bmpBuffer, width, height, bytesPerPixel);
84
85 ds->write(bmpBuffer, bmpDataSize);
86 ds->close();
87
88 bs_deleteN(bmpBuffer, bmpDataSize);
89 }
90
91 void Debug::_triggerCallbacks()
92 {
93 LogEntry entry;
94 while (mLog.getUnreadEntry(entry))
95 {
96 onLogEntryAdded(entry);
97 }
98
99 UINT64 hash = mLog.getHash();
100 if(mLogHash != hash)
101 {
102 onLogModified();
103 mLogHash = hash;
104 }
105 }
106
107 void Debug::saveLog(const Path& path) const
108 {
109 static const char* style =
110 R"(html {
111 font-family: sans-serif;
112}
113
114table
115{
116 border-collapse: collapse;
117 border-spacing: 0;
118 empty-cells: show;
119 border: 1px solid #cbcbcb;
120 width:100%;
121 table-layout:fixed;
122}
123
124table caption
125{
126 color: #000;
127 font: italic 85%/1 arial, sans-serif;
128 padding: 1em 0;
129 text-align: center;
130}
131
132table td,
133table th
134{
135 border-left: 1px solid #cbcbcb;/* inner column border */
136 border-width: 0 0 0 1px;
137 font-size: inherit;
138 margin: 0;
139 overflow: visible; /*to make ths where the title is really long work*/
140 padding: 0.5em 1em; /* cell padding */
141}
142
143table td:first-child,
144table th:first-child
145{
146 border-left-width: 0;
147}
148
149table thead
150{
151 background-color: #e0e0e0;
152 color: #000;
153 text-align: left;
154 vertical-align: bottom;
155}
156
157table td
158{
159 background-color: transparent;
160 word-wrap:break-word;
161 vertical-align: top;
162 color: #7D7D7D;
163}
164
165.debug-row td {
166 background-color: #FFFFFF;
167}
168
169.debug-alt-row td {
170 background-color: #f2f2f2;
171}
172
173.warn-row td {
174 background-color: #ffc016;
175 color: #5F5F5F;
176}
177
178.warn-alt-row td {
179 background-color: #fdcb41;
180 color: #5F5F5F;
181}
182
183.error-row td {
184 background-color: #9f1621;
185 color: #9F9F9F;
186}
187
188.error-alt-row td {
189 background-color: #ae1621;
190 color: #9F9F9F;
191}
192)";
193
194 static const char* htmlPreStyleHeader =
195 R"(<!DOCTYPE html>
196<html lang="en">
197<head>
198<style type="text/css">
199)";
200
201 #if BS_IS_BANSHEE3D
202 static const char* htmlPostStyleHeader =
203 R"(</style>
204<title>Banshee Engine Log</title>
205</head>
206<body>
207)";
208 #else
209 static const char* htmlPostStyleHeader =
210 R"(</style>
211<title>bs::framework Log</title>
212</head>
213<body>
214)";
215 #endif
216
217 static const char* htmlEntriesTableHeader =
218 R"(<table border="1" cellpadding="1" cellspacing="1">
219 <thead>
220 <tr>
221 <th scope="col" style="width:60px">Type</th>
222 <th scope="col" style="width:70px">Time</th>
223 <th scope="col">Description</th>
224 </tr>
225 </thead>
226 <tbody>
227)";
228
229 static const char* htmlFooter =
230 R"( </tbody>
231</table>
232</body>
233</html>)";
234
235 StringStream stream;
236 stream << htmlPreStyleHeader;
237 stream << style;
238 stream << htmlPostStyleHeader;
239 #if BS_IS_BANSHEE3D
240 stream << "<h1>Banshee Engine Log</h1>\n";
241 #else
242 stream << "<h1>bs::framework Log</h1>\n";
243 #endif
244
245 stream << "<h2>System information</h2>\n";
246
247 // Write header information
248 stream << "<p>bs::framework version: " << BS_VERSION_MAJOR << "." << BS_VERSION_MINOR <<"." << BS_VERSION_PATCH << "</p>\n";
249
250 if(Time::isStarted())
251 stream << "<p>Started on: " << gTime().getAppStartUpDateString(false) << "</p>\n";
252
253 SystemInfo systemInfo = PlatformUtility::getSystemInfo();
254 stream << "<p>OS version: " << systemInfo.osName << " " << (systemInfo.osIs64Bit ? "64-bit" : "32-bit") << "</p>\n";
255 stream << "<h3>CPU information:</h3>\n";
256 stream << "<p>CPU vendor: " << systemInfo.cpuManufacturer << "</p>\n";
257 stream << "<p>CPU name: " << systemInfo.cpuModel << "</p>\n";
258 stream << "<p>CPU clock speed: " << systemInfo.cpuClockSpeedMhz << "Mhz</p>\n";
259 stream << "<p>CPU core count: " << systemInfo.cpuNumCores << "</p>\n";
260
261 stream << "<h3>GPU List:</h3>\n";
262 if(systemInfo.gpuInfo.numGPUs == 1)
263 stream << "<p>GPU: " << systemInfo.gpuInfo.names[0] << "</p>\n";
264 else
265 {
266 for(UINT32 i = 0; i < systemInfo.gpuInfo.numGPUs; i++)
267 stream << "<p>GPU #" << i << ": " << systemInfo.gpuInfo.names[i] << "</p>\n";
268 }
269
270 // Write log entries
271 stream << "<h2>Log entries</h2>\n";
272 stream << htmlEntriesTableHeader;
273
274 bool alternate = false;
275 Vector<LogEntry> entries = mLog.getAllEntries();
276 for (auto& entry : entries)
277 {
278 String channelName;
279 if (entry.getChannel() == (UINT32)DebugChannel::Error || entry.getChannel() == (UINT32)DebugChannel::CompilerError)
280 {
281 if (!alternate)
282 stream << R"( <tr class="error-row">)" << std::endl;
283 else
284 stream << R"( <tr class="error-alt-row">)" << std::endl;
285
286 stream << R"( <td>Error</td>)" << std::endl;
287 }
288 else if (entry.getChannel() == (UINT32)DebugChannel::Warning || entry.getChannel() == (UINT32)DebugChannel::CompilerWarning)
289 {
290 if (!alternate)
291 stream << R"( <tr class="warn-row">)" << std::endl;
292 else
293 stream << R"( <tr class="warn-alt-row">)" << std::endl;
294
295 stream << R"( <td>Warning</td>)" << std::endl;
296 }
297 else
298 {
299 if (!alternate)
300 stream << R"( <tr class="debug-row">)" << std::endl;
301 else
302 stream << R"( <tr class="debug-alt-row">)" << std::endl;
303
304 stream << R"( <td>Debug</td>)" << std::endl;
305 }
306
307 stream << R"( <td>)" << entry.getLocalTime() << "</td>" << std::endl;
308
309 String parsedMessage = StringUtil::replaceAll(entry.getMessage(), "\n", "<br>\n");
310
311 stream << R"( <td>)" << parsedMessage << "</td>" << std::endl;
312 stream << R"( </tr>)" << std::endl;
313
314 alternate = !alternate;
315 }
316
317 stream << htmlFooter;
318
319 SPtr<DataStream> fileStream = FileSystem::createAndOpenFile(path);
320 fileStream->writeString(stream.str());
321 }
322
323 BS_UTILITY_EXPORT Debug& gDebug()
324 {
325 static Debug debug;
326 return debug;
327 }
328}
329