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 | |
15 | void 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 |
30 | void logToIDEConsole(const bs::String& message, const char* channel) |
31 | { |
32 | std::cout << "[" << channel << "] " << message << std::endl; |
33 | } |
34 | #endif |
35 | |
36 | namespace 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 | |
114 | table |
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 | |
124 | table caption |
125 | { |
126 | color: #000; |
127 | font: italic 85%/1 arial, sans-serif; |
128 | padding: 1em 0; |
129 | text-align: center; |
130 | } |
131 | |
132 | table td, |
133 | table 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 | |
143 | table td:first-child, |
144 | table th:first-child |
145 | { |
146 | border-left-width: 0; |
147 | } |
148 | |
149 | table thead |
150 | { |
151 | background-color: #e0e0e0; |
152 | color: #000; |
153 | text-align: left; |
154 | vertical-align: bottom; |
155 | } |
156 | |
157 | table 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* = |
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* = |
210 | R"(</style> |
211 | <title>bs::framework Log</title> |
212 | </head> |
213 | <body> |
214 | )" ; |
215 | #endif |
216 | |
217 | static const char* = |
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* = |
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 | |