1 /* Copyright (C) 2015 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/>.
19 * Implementation of profile display (containing only display routines,
20 * the data model(s) are implemented elsewhere).
23 #include "precompiled.h"
28 #include "ProfileViewer.h"
30 #include "graphics/FontMetrics.h"
31 #include "gui/GUIutil.h"
32 #include "graphics/ShaderManager.h"
33 #include "graphics/TextRenderer.h"
34 #include "ps/CLogger.h"
35 #include "ps/Filesystem.h"
36 #include "ps/Hotkey.h"
37 #include "ps/Profile.h"
38 #include "lib/external_libraries/libsdl.h"
39 #include "renderer/Renderer.h"
40 #include "scriptinterface/ScriptInterface.h"
42 extern int g_xres
, g_yres
;
44 struct CProfileViewerInternals
46 NONCOPYABLE(CProfileViewerInternals
); // because of the ofstream
48 CProfileViewerInternals() {}
50 /// Whether the profiling display is currently visible
53 /// List of root tables
54 std::vector
<AbstractProfileTable
*> rootTables
;
56 /// Path from a root table (path[0]) to the currently visible table (path[size-1])
57 std::vector
<AbstractProfileTable
*> path
;
60 void TableIsDeleted(AbstractProfileTable
* table
);
61 void NavigateTree(int id
);
63 /// File for saved profile output (reset when the game is restarted)
64 std::ofstream outputStream
;
68 ///////////////////////////////////////////////////////////////////////////////////////////////
69 // AbstractProfileTable implementation
71 AbstractProfileTable::~AbstractProfileTable()
73 if (CProfileViewer::IsInitialised())
75 g_ProfileViewer
.m
->TableIsDeleted(this);
80 ///////////////////////////////////////////////////////////////////////////////////////////////
81 // CProfileViewer implementation
84 // AbstractProfileTable got deleted, make sure we have no dangling pointers
85 void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable
* table
)
87 for(int idx
= (int)rootTables
.size()-1; idx
>= 0; --idx
)
89 if (rootTables
[idx
] == table
)
90 rootTables
.erase(rootTables
.begin() + idx
);
93 for(size_t idx
= 0; idx
< path
.size(); ++idx
)
95 if (path
[idx
] != table
)
98 path
.erase(path
.begin() + idx
, path
.end());
100 profileVisible
= false;
105 // Move into child tables or return to parent tables based on the given number
106 void CProfileViewerInternals::NavigateTree(int id
)
115 AbstractProfileTable
* table
= path
[path
.size() - 1];
116 size_t numrows
= table
->GetNumberRows();
118 for(size_t row
= 0; row
< numrows
; ++row
)
120 AbstractProfileTable
* child
= table
->GetChild(row
);
128 path
.push_back(child
);
136 // Construction/Destruction
137 CProfileViewer::CProfileViewer()
139 m
= new CProfileViewerInternals
;
140 m
->profileVisible
= false;
143 CProfileViewer::~CProfileViewer()
150 void CProfileViewer::RenderProfile()
152 if (!m
->profileVisible
)
157 m
->profileVisible
= false;
161 PROFILE3_GPU("profile viewer");
164 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
166 AbstractProfileTable
* table
= m
->path
[m
->path
.size() - 1];
167 const std::vector
<ProfileColumn
>& columns
= table
->GetColumns();
168 size_t numrows
= table
->GetNumberRows();
170 CStrIntern
font_name("mono-stroke-10");
171 CFontMetrics
font(font_name
);
172 int lineSpacing
= font
.GetLineSpacing();
175 GLint estimate_height
;
176 GLint estimate_width
;
179 for(size_t i
= 0; i
< columns
.size(); ++i
)
180 estimate_width
+= (GLint
)columns
[i
].width
;
182 estimate_height
= 3 + (GLint
)numrows
;
183 if (m
->path
.size() > 1)
184 estimate_height
+= 2;
185 estimate_height
= lineSpacing
*estimate_height
;
187 CShaderTechniquePtr solidTech
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_solid
);
188 solidTech
->BeginPass();
189 CShaderProgramPtr solidShader
= solidTech
->GetShader();
191 solidShader
->Uniform(str_color
, 0.0f
, 0.0f
, 0.0f
, 0.5f
);
193 CMatrix3D transform
= GetDefaultGuiMatrix();
194 solidShader
->Uniform(str_transform
, transform
);
196 float backgroundVerts
[] = {
197 (float)estimate_width
, 0.0f
,
199 0.0f
, (float)estimate_height
,
200 0.0f
, (float)estimate_height
,
201 (float)estimate_width
, (float)estimate_height
,
202 (float)estimate_width
, 0.0f
204 solidShader
->VertexPointer(2, GL_FLOAT
, 0, backgroundVerts
);
205 solidShader
->AssertPointersBound();
206 glDrawArrays(GL_TRIANGLES
, 0, 6);
208 transform
.PostTranslate(22.0f
, lineSpacing
*3.0f
, 0.0f
);
209 solidShader
->Uniform(str_transform
, transform
);
211 // Draw row backgrounds
212 for (size_t row
= 0; row
< numrows
; ++row
)
215 solidShader
->Uniform(str_color
, 1.0f
, 1.0f
, 1.0f
, 0.1f
);
217 solidShader
->Uniform(str_color
, 0.0f
, 0.0f
, 0.0f
, 0.1f
);
221 estimate_width
-22.f
, 2.f
,
222 estimate_width
-22.f
, 2.f
-lineSpacing
,
224 estimate_width
-22.f
, 2.f
-lineSpacing
,
225 -22.f
, 2.f
-lineSpacing
,
228 solidShader
->VertexPointer(2, GL_FLOAT
, 0, rowVerts
);
229 solidShader
->AssertPointersBound();
230 glDrawArrays(GL_TRIANGLES
, 0, 6);
232 transform
.PostTranslate(0.0f
, lineSpacing
, 0.0f
);
233 solidShader
->Uniform(str_transform
, transform
);
236 solidTech
->EndPass();
238 // Print table and column titles
240 CShaderTechniquePtr textTech
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_text
);
241 textTech
->BeginPass();
243 CTextRenderer
textRenderer(textTech
->GetShader());
244 textRenderer
.Font(font_name
);
245 textRenderer
.Color(1.0f
, 1.0f
, 1.0f
);
247 textRenderer
.PrintfAt(2.0f
, lineSpacing
, L
"%hs", table
->GetTitle().c_str());
249 textRenderer
.Translate(22.0f
, lineSpacing
*2.0f
, 0.0f
);
252 for (size_t col
= 0; col
< columns
.size(); ++col
)
254 CStrW text
= columns
[col
].title
.FromUTF8();
256 font
.CalculateStringSize(text
.c_str(), w
, h
);
259 if (col
> 0) // right-align all but the first column
260 x
+= columns
[col
].width
- w
;
261 textRenderer
.Put(x
, 0.0f
, text
.c_str());
263 colX
+= columns
[col
].width
;
266 textRenderer
.Translate(0.0f
, lineSpacing
, 0.0f
);
269 int currentExpandId
= 1;
271 for (size_t row
= 0; row
< numrows
; ++row
)
273 if (table
->IsHighlightRow(row
))
274 textRenderer
.Color(1.0f
, 0.5f
, 0.5f
);
276 textRenderer
.Color(1.0f
, 1.0f
, 1.0f
);
278 if (table
->GetChild(row
))
280 textRenderer
.PrintfAt(-15.0f
, 0.0f
, L
"%d", currentExpandId
);
285 for (size_t col
= 0; col
< columns
.size(); ++col
)
287 CStrW text
= table
->GetCellText(row
, col
).FromUTF8();
289 font
.CalculateStringSize(text
.c_str(), w
, h
);
292 if (col
> 0) // right-align all but the first column
293 x
+= columns
[col
].width
- w
;
294 textRenderer
.Put(x
, 0.0f
, text
.c_str());
296 colX
+= columns
[col
].width
;
299 textRenderer
.Translate(0.0f
, lineSpacing
, 0.0f
);
302 textRenderer
.Color(1.0f
, 1.0f
, 1.0f
);
304 if (m
->path
.size() > 1)
306 textRenderer
.Translate(0.0f
, lineSpacing
, 0.0f
);
307 textRenderer
.Put(-15.0f
, 0.0f
, L
"0");
308 textRenderer
.Put(0.0f
, 0.0f
, L
"back to parent");
311 textRenderer
.Render();
316 glEnable(GL_DEPTH_TEST
);
321 InReaction
CProfileViewer::Input(const SDL_Event_
* ev
)
327 if (!m
->profileVisible
)
330 int k
= ev
->ev
.key
.keysym
.sym
;
331 if (k
>= SDLK_0
&& k
<= SDLK_9
)
333 m
->NavigateTree(k
- SDLK_0
);
339 std::string hotkey
= static_cast<const char*>(ev
->ev
.user
.data1
);
341 if( hotkey
== "profile.toggle" )
343 if (!m
->profileVisible
)
345 if (m
->rootTables
.size())
347 m
->profileVisible
= true;
348 m
->path
.push_back(m
->rootTables
[0]);
355 for(i
= 0; i
< m
->rootTables
.size(); ++i
)
357 if (m
->rootTables
[i
] == m
->path
[0])
363 if (i
< m
->rootTables
.size())
365 m
->path
.push_back(m
->rootTables
[i
]);
369 m
->profileVisible
= false;
372 return( IN_HANDLED
);
374 else if( hotkey
== "profile.save" )
377 return( IN_HANDLED
);
384 InReaction
CProfileViewer::InputThunk(const SDL_Event_
* ev
)
386 if (CProfileViewer::IsInitialised())
387 return g_ProfileViewer
.Input(ev
);
393 // Add a table to the list of roots
394 void CProfileViewer::AddRootTable(AbstractProfileTable
* table
, bool front
)
397 m
->rootTables
.insert(m
->rootTables
.begin(), table
);
399 m
->rootTables
.push_back(table
);
407 WriteTable(std::ofstream
& f
) : f(f
) {}
409 void operator() (AbstractProfileTable
* table
)
411 std::vector
<CStr
> data
; // 2d array of (rows+head)*columns elements
413 const std::vector
<ProfileColumn
>& columns
= table
->GetColumns();
415 // Add column headers to 'data'
416 for (std::vector
<ProfileColumn
>::const_iterator col_it
= columns
.begin();
417 col_it
!= columns
.end(); ++col_it
)
418 data
.push_back(col_it
->title
);
420 // Recursively add all profile data to 'data'
421 WriteRows(1, table
, data
);
423 // Calculate the width of each column ( = the maximum width of
424 // any value in that column)
425 std::vector
<size_t> columnWidths
;
426 size_t cols
= columns
.size();
427 for (size_t c
= 0; c
< cols
; ++c
)
430 for (size_t i
= c
; i
< data
.size(); i
+= cols
)
431 max
= std::max(max
, data
[i
].length());
432 columnWidths
.push_back(max
);
435 // Output data as a formatted table:
437 f
<< "\n\n" << table
->GetTitle() << "\n";
439 if (cols
== 0) // avoid divide-by-zero
442 for (size_t r
= 0; r
< data
.size()/cols
; ++r
)
444 for (size_t c
= 0; c
< cols
; ++c
)
445 f
<< (c
? " | " : "\n")
446 << data
[r
*cols
+ c
].Pad(PS_TRIM_RIGHT
, columnWidths
[c
]);
448 // Add dividers under some rows. (Currently only the first, since
449 // that contains the column headers.)
451 for (size_t c
= 0; c
< cols
; ++c
)
452 f
<< (c
? "-|-" : "\n")
453 << CStr::Repeat("-", columnWidths
[c
]);
457 void WriteRows(int indent
, AbstractProfileTable
* table
, std::vector
<CStr
>& data
)
459 const std::vector
<ProfileColumn
>& columns
= table
->GetColumns();
461 for (size_t r
= 0; r
< table
->GetNumberRows(); ++r
)
463 // Do pretty tree-structure indenting
464 CStr indentation
= CStr::Repeat("| ", indent
-1);
465 if (r
+1 == table
->GetNumberRows())
470 for (size_t c
= 0; c
< columns
.size(); ++c
)
472 data
.push_back(indentation
+ table
->GetCellText(r
, c
));
474 data
.push_back(table
->GetCellText(r
, c
));
476 if (table
->GetChild(r
))
477 WriteRows(indent
+1, table
->GetChild(r
), data
);
482 const WriteTable
& operator=(const WriteTable
&);
487 ScriptInterface
& m_ScriptInterface
;
488 JS::PersistentRooted
<JS::Value
> m_Root
;
489 DumpTable(ScriptInterface
& scriptInterface
, JS::HandleValue root
) :
490 m_ScriptInterface(scriptInterface
), m_Root(scriptInterface
.GetJSRuntime(), root
)
494 // std::for_each requires a move constructor and the use of JS::PersistentRooted<T> apparently breaks a requirement for an
495 // automatic move constructor
496 DumpTable(DumpTable
&& original
) :
497 m_ScriptInterface(original
.m_ScriptInterface
),
498 m_Root(original
.m_ScriptInterface
.GetJSRuntime(), original
.m_Root
.get())
502 void operator() (AbstractProfileTable
* table
)
504 JSContext
* cx
= m_ScriptInterface
.GetContext();
505 JSAutoRequest
rq(cx
);
507 JS::RootedValue
t(cx
);
508 JS::RootedValue
rows(cx
, DumpRows(table
));
509 m_ScriptInterface
.Eval(L
"({})", &t
);
510 m_ScriptInterface
.SetProperty(t
, "cols", DumpCols(table
));
511 m_ScriptInterface
.SetProperty(t
, "data", rows
);
513 m_ScriptInterface
.SetProperty(m_Root
, table
->GetTitle().c_str(), t
);
516 std::vector
<std::string
> DumpCols(AbstractProfileTable
* table
)
518 std::vector
<std::string
> titles
;
520 const std::vector
<ProfileColumn
>& columns
= table
->GetColumns();
522 for (size_t c
= 0; c
< columns
.size(); ++c
)
523 titles
.push_back(columns
[c
].title
);
528 JS::Value
DumpRows(AbstractProfileTable
* table
)
530 JSContext
* cx
= m_ScriptInterface
.GetContext();
531 JSAutoRequest
rq(cx
);
533 JS::RootedValue
data(cx
);
534 m_ScriptInterface
.Eval("({})", &data
);
536 const std::vector
<ProfileColumn
>& columns
= table
->GetColumns();
538 for (size_t r
= 0; r
< table
->GetNumberRows(); ++r
)
540 JS::RootedValue
row(cx
);
541 m_ScriptInterface
.Eval("([])", &row
);
542 m_ScriptInterface
.SetProperty(data
, table
->GetCellText(r
, 0).c_str(), row
);
544 if (table
->GetChild(r
))
546 JS::RootedValue
childRows(cx
, DumpRows(table
->GetChild(r
)));
547 m_ScriptInterface
.SetPropertyInt(row
, 0, childRows
);
550 for (size_t c
= 1; c
< columns
.size(); ++c
)
551 m_ScriptInterface
.SetPropertyInt(row
, c
, table
->GetCellText(r
, c
));
558 const DumpTable
& operator=(const DumpTable
&);
561 bool SortByName(AbstractProfileTable
* a
, AbstractProfileTable
* b
)
563 return (a
->GetName() < b
->GetName());
567 void CProfileViewer::SaveToFile()
569 // Open the file, if necessary. If this method is called several times,
570 // the profile results will be appended to the previous ones from the same
572 if (! m
->outputStream
.is_open())
574 // Open the file. (It will be closed when the CProfileViewer
575 // destructor is called.)
576 OsPath path
= psLogDir()/"profile.txt";
577 m
->outputStream
.open(OsString(path
).c_str(), std::ofstream::out
| std::ofstream::trunc
);
579 if (m
->outputStream
.fail())
581 LOGERROR("Failed to open profile log file");
586 LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path
.string8());
592 m
->outputStream
<< "================================================================\n\n";
593 m
->outputStream
<< "PS profiler snapshot - " << asctime(localtime(&t
));
595 std::vector
<AbstractProfileTable
*> tables
= m
->rootTables
;
596 sort(tables
.begin(), tables
.end(), SortByName
);
597 for_each(tables
.begin(), tables
.end(), WriteTable(m
->outputStream
));
599 m
->outputStream
<< "\n\n================================================================\n";
600 m
->outputStream
.flush();
603 JS::Value
CProfileViewer::SaveToJS(ScriptInterface
& scriptInterface
)
605 JSContext
* cx
= scriptInterface
.GetContext();
606 JSAutoRequest
rq(cx
);
608 JS::RootedValue
root(cx
);
609 scriptInterface
.Eval("({})", &root
);
611 std::vector
<AbstractProfileTable
*> tables
= m
->rootTables
;
612 sort(tables
.begin(), tables
.end(), SortByName
);
613 for_each(tables
.begin(), tables
.end(), DumpTable(scriptInterface
, root
));
618 void CProfileViewer::ShowTable(const CStr
& table
)
622 if (table
.length() > 0)
624 for (size_t i
= 0; i
< m
->rootTables
.size(); ++i
)
626 if (m
->rootTables
[i
]->GetName() == table
)
628 m
->path
.push_back(m
->rootTables
[i
]);
629 m
->profileVisible
= true;
635 // No matching table found, so don't display anything
636 m
->profileVisible
= false;